// Copyright (C) 2016 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.httpd.raw; import static com.google.common.base.MoreObjects.firstNonNull; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.escape.Escaper; import com.google.common.html.HtmlEscapers; import com.google.common.io.ByteStreams; import com.google.gerrit.common.TimeUtil; import com.google.gwtexpui.server.CacheHeaders; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Properties; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.util.RawParseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BazelBuild { private static final Logger log = LoggerFactory.getLogger(BazelBuild.class); private final Path sourceRoot; public BazelBuild(Path sourceRoot) { this.sourceRoot = sourceRoot; } // builds the given label. public void build(Label label) throws IOException, BuildFailureException { ProcessBuilder proc = newBuildProcess(label); proc.directory(sourceRoot.toFile()).redirectErrorStream(true); log.info("building " + label.fullName()); long start = TimeUtil.nowMs(); Process rebuild = proc.start(); byte[] out; try (InputStream in = rebuild.getInputStream()) { out = ByteStreams.toByteArray(in); } finally { rebuild.getOutputStream().close(); } int status; try { status = rebuild.waitFor(); } catch (InterruptedException e) { throw new InterruptedIOException("interrupted waiting for " + proc.toString()); } if (status != 0) { log.warn("build failed: " + new String(out, UTF_8)); throw new BuildFailureException(out); } long time = TimeUtil.nowMs() - start; log.info(String.format("UPDATED %s in %.3fs", label.fullName(), time / 1000.0)); } // Represents a label in bazel. static class Label { protected final String pkg; protected final String name; public String fullName() { return "//" + pkg + ":" + name; } @Override public String toString() { return fullName(); } // Label in Bazel style. Label(String pkg, String name) { this.name = name; this.pkg = pkg; } } static class BuildFailureException extends Exception { private static final long serialVersionUID = 1L; final byte[] why; BuildFailureException(byte[] why) { this.why = why; } public void display(String rule, HttpServletResponse res) throws IOException { res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); res.setContentType("text/html"); res.setCharacterEncoding(UTF_8.name()); CacheHeaders.setNotCacheable(res); Escaper html = HtmlEscapers.htmlEscaper(); try (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>"); } } } private Properties loadBuildProperties(Path propPath) throws IOException { Properties properties = new Properties(); try (InputStream in = Files.newInputStream(propPath)) { properties.load(in); } catch (NoSuchFileException e) { // Ignore; will be run from PATH, with a descriptive error if it fails. } return properties; } private ProcessBuilder newBuildProcess(Label label) throws IOException { Properties properties = loadBuildProperties(sourceRoot.resolve(".bazel_path")); String bazel = firstNonNull(properties.getProperty("bazel"), "bazel"); ProcessBuilder proc = new ProcessBuilder(bazel, "build", label.fullName()); if (properties.containsKey("PATH")) { proc.environment().put("PATH", properties.getProperty("PATH")); } return proc; } /** returns the root relative path to the artifact for the given label */ public Path targetPath(Label l) { return sourceRoot.resolve("bazel-bin").resolve(l.pkg).resolve(l.name); } /** Label for the agent specific GWT zip. */ public Label gwtZipLabel(String agent) { return new Label("gerrit-gwtui", "ui_" + agent + ".zip"); } /** Label for the polygerrit component zip. */ public Label polygerritComponents() { return new Label("polygerrit-ui", "polygerrit_components.bower_components.zip"); } /** Label for the fonts zip file. */ public Label fontZipLabel() { return new Label("polygerrit-ui", "fonts.zip"); } }