Display UI build failures in browser
Instead of crashing the Jetty daemon when there is a UI build failure,
keep the daemon running and display the output from Buck in the web
browser. This allows for a slightly quicker fix and retry cycle,
especially for minor typos in ui.xml files. Simply edit the offending
source and reload, without needing to relaunch the daemon.
Change-Id: Iac31374d4efd44c4c6a58ce08497fbf24dd6dfa9
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index c5c812a..3d284e9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -17,7 +17,10 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.common.base.Charsets;
import com.google.common.base.Objects;
+import com.google.common.escape.Escaper;
+import com.google.common.html.HtmlEscapers;
import com.google.common.io.ByteStreams;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.launcher.GerritLauncher;
@@ -27,6 +30,7 @@
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtexpui.linker.server.UserAgentRule;
+import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
@@ -58,6 +62,7 @@
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.util.RawParseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,6 +73,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
+import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -90,6 +96,7 @@
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
@Singleton
public class JettyServer {
@@ -582,10 +589,16 @@
FilterChain chain) throws IOException, ServletException {
String pkg = "gerrit-gwtui";
String target = "ui_" + rule.select((HttpServletRequest) request);
+ String rule = "//" + pkg + ":" + target;
File zip = new File(new File(gen, pkg), target + ".zip");
synchronized (this) {
- build(root, gen, "//" + pkg + ":" + target);
+ try {
+ build(root, gen, rule);
+ } catch (BuildFailureException e) {
+ displayFailure(rule, e.why, (HttpServletResponse) res);
+ return;
+ }
if (!target.equals(lastTarget) || lastTime != zip.lastModified()) {
lastTarget = target;
@@ -597,6 +610,24 @@
chain.doFilter(request, res);
}
+ private void displayFailure(String rule, byte[] why, HttpServletResponse res)
+ throws IOException {
+ res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ res.setContentType("text/html");
+ res.setCharacterEncoding(Charsets.UTF_8.name());
+ CacheHeaders.setNotCacheable(res);
+
+ Escaper html = HtmlEscapers.htmlEscaper();
+ PrintWriter w = res.getWriter();
+ w.write("<html><title>BUILD FAILED</title><body>");
+ w.format("<h1>%s FAILED</h1>", html.escape(rule));
+ w.write("<pre>");
+ w.write(html.escape(RawParseUtils.decode(why)));
+ w.write("</pre>");
+ w.write("</body></html>");
+ w.close();
+ }
+
@Override
public void init(FilterConfig config) {
}
@@ -609,7 +640,7 @@
}
private static void build(File root, File gen, String target)
- throws IOException {
+ throws IOException, BuildFailureException {
log.info("buck build " + target);
Properties properties = loadBuckProperties(gen);
String buck = Objects.firstNonNull(properties.getProperty("buck"), "buck");
@@ -637,9 +668,7 @@
throw new InterruptedIOException("interrupted waiting for " + buck);
}
if (status != 0) {
- System.err.write(out);
- System.err.println();
- System.exit(status);
+ throw new BuildFailureException(out);
}
long time = TimeUtil.nowMs() - start;
@@ -658,4 +687,13 @@
}
return properties;
}
+
+ @SuppressWarnings("serial")
+ private static class BuildFailureException extends Exception {
+ final byte[] why;
+
+ BuildFailureException(byte[] why) {
+ this.why = why;
+ }
+ }
}