| // 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.base.Joiner; |
| import com.google.common.escape.Escaper; |
| import com.google.common.flogger.FluentLogger; |
| import com.google.common.html.HtmlEscapers; |
| import com.google.common.io.ByteStreams; |
| import com.google.gerrit.launcher.GerritLauncher; |
| import com.google.gerrit.server.util.time.TimeUtil; |
| import com.google.gerrit.util.http.CacheHeaders; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InterruptedIOException; |
| import java.io.PrintWriter; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Properties; |
| import javax.servlet.http.HttpServletResponse; |
| import org.eclipse.jgit.util.RawParseUtils; |
| |
| public class BazelBuild { |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| 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); |
| logger.atInfo().log("building %s", 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) { |
| String msg = "interrupted waiting for: " + Joiner.on(' ').join(proc.command()); |
| logger.atSevere().withCause(e).log(msg); |
| throw new InterruptedIOException(msg); |
| } |
| if (status != 0) { |
| logger.atWarning().log("build failed: %s", new String(out, UTF_8)); |
| throw new BuildFailureException(out); |
| } |
| |
| long time = TimeUtil.nowMs() - start; |
| logger.atInfo().log("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 ProcessBuilder newBuildProcess(Label label) throws IOException { |
| Properties properties = GerritLauncher.loadBuildProperties(sourceRoot.resolve(".bazel_path")); |
| String bazel = firstNonNull(properties.getProperty("bazel"), "bazel"); |
| List<String> cmd = new ArrayList<>(); |
| cmd.add(bazel); |
| cmd.add("build"); |
| if (GerritLauncher.isJdk9OrLater()) { |
| String v = GerritLauncher.getJdkVersionPostJdk8(); |
| cmd.add("--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v); |
| cmd.add("--java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v); |
| } |
| cmd.add(label.fullName()); |
| ProcessBuilder proc = new ProcessBuilder(cmd); |
| 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 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"); |
| } |
| } |