Building Gerrit, David Ostrovsky

Gerrit User Conference 2016

Outline

Gerrit project

Provide outstanding development environment

Challenges

Challenges: Upgrading the dependencies is not trivial

Example: Bump Jetty version to 9.X release

  • Classpath collision with GWT, because it depends on Jetty 8
  • Short term solution: implement a workaround
    • strip outdated Jetty clases from gwt-dev.jar
    • Fork coderserver’s WebSever.java from GWT project, adjust to Jetty 9.x
  • Long term solution: Fix upstream
    • Uploaded a patch to GWT to bump Jetty
    • Dependency on HtmlJunit, that depends on Jetty 8, because of WebSocket
    • Uploaded a patch to HtmlUnit to bump to Jetty 9.x with updated WebSocket integration (required to update HtmlUnit to Java 7 first ;-)
    • Wait for WebSocket to pubish new release
    • Bump HtmlUnit to 2.19 and Jetty to 9.X in GWT
    • 2 years efforts, 36 patch sets (https://gwt-review.googlesource.com/7857)
  • Revert short term solution: remove coderserver’s WebSever.java from Gerrit

Maven, … and how Gerrit project escaped it?

img/i-am-compiling.png

Shawn implemented the Gerrit build in Buck

img/maven-image-shawn.png

Buck Outline

History

Build Gerrit with Buck

Build Gerrit with Buck

Pros

  • incredibly fast, reliable and accurate (buckd, watchman)
  • local and remote caching
  • tests only run when needed
  • integration with Eclipse

Build Gerrit with Buck

Some cons

  • cannot be installed
  • bootstraping requires Ant
  • recompile on branch switch (very annoying)
  • broken cell support (jgit cell implementation revert)
  • restricted re-use of existing build rules (FB monorepo heritage)

Buck evolution:

Buck evolution: One single build incident in 4 years

Bazel

Introducing Bazel

img/bazel-choose-two.png

Introducing Bazel 1/4

Introducing Bazel 2/4

Introducing Bazel 3/4

Introducing Bazel: 4/4

Bazel command (same as in Buck)

Build phases

Bazel WORKSPACE file / BUILD files

Show me the code

img/show-me-the-code.png

Example: Printy

My application

package org.gerritcon.mv2016;

import com.google.common.base.Joiner;

public class Printy {
  public static void main(String[] argv) {
    new Printy().mainImpl(argv);
  }

  void mainImpl(String[] argv) throws IOException {
    System.out.println(Joiner.on(' ').join(argv));
  }
}

Building printy

WORKSPACE:

maven_jar(
  name = 'guava',
  artifact = 'com.google.guava:guava:19.0',
  sha1 = '6ce200f6b23222af3d8abb6b6459e6c44f4bb0e9',
)

Building printy

BUILD

java_library(
    name = "printy_lib",
    srcs = glob(["src/main/java/**/*.java"]),
    deps = ["@guava//jar"],
)

java_binary(
    name = "printy",
    deploy_manifest_lines = [
        "Implementation-Version: 1.0",
        "Implementation-Vendor: Gerrit User Conference 2016",
    ],
    main_class = "org.gerritcon.mv2016.Printy",
    runtime_deps = [":printy_lib"],
)

Running printy

  $ bazel run printy Hello Bazel!
INFO: (11-12 07:28:16.709) Found 1 target...
Target //:printy up-to-date:
  bazel-bin/printy.jar
  bazel-bin/printy
INFO: (11-12 07:28:17.865) Running command line: bazel-bin/printy Hello 'Bazel!'
Hello Bazel!

Enhance printy

Add --version argument, and read "Implementation-Version" from manifest

  void mainImpl(String[] argv) throws IOException {
    if (argv.length > 0 && argv[0].equals("--version")) {
      printVersion();
      return;
    }
    System.out.println(Joiner.on(' ').join(argv));
  }

  void printVersion() throws IOException {
    URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
    URL url = cl.findResource("META-INF/MANIFEST.MF");
    Manifest manifest = new Manifest(url.openStream());
    Attributes main = manifest.getMainAttributes();
    String version = main.getValue("Implementation-Version");
    if (Strings.isNullOrEmpty(version)) {
      System.err.println("no version specified");
    } else {
      System.err.println(version);
    }
  }

Test enhanced version

$ bazel run printy -- --version
INFO: (11-12 07:38:07.812) Found 1 target...
Target //:printy up-to-date:
  bazel-bin/printy.jar
  bazel-bin/printy
INFO: (11-12 07:38:07.839) Elapsed time: 0.111s, Critical Path: 0.01s

INFO: (11-12 07:38:07.840) Running command line: bazel-bin/printy --version
no version specified

Test enhanced version: let’s look even deeper

$ bazel build printy
INFO: (11-12 07:42:26.843) Found 1 target...
Target //:printy up-to-date:
  bazel-bin/printy.jar
  bazel-bin/printy
INFO: (11-12 07:42:26.873) Elapsed time: 0.124s, Critical Path: 0.01s

$ file bazel-bin/printy
bazel-bin/printy: Bourne-Again shell script, ASCII text executable

# add verbose output and run again:

$ bazel-bin/printy -- --version
[...] # shorten some paths
+ [...]java -classpath [...]/libprinty_lib.jar:[...]/guava-19.0.jar org.gerritcon.mv2016.Printy -- --version

Only build what you have asked for

Create ueber JAR:

$ bazel build printy_deploy.jar
INFO: (11-12 07:47:46.033) Found 1 target...
Target //:printy_deploy.jar up-to-date:
  bazel-bin/printy_deploy.jar
INFO: (11-12 07:47:46.405) Elapsed time: 0.435s, Critical Path: 0.35s

Test ueber jar:

$ java -jar bazel-bin/printy_deploy.jar --version
1.0

Enhance printy even more

Stamping version from git describe:

  • tools/bazel.rc:
build --workspace_status_command=./tools/workspace-status.sh
  • tools/workspace-status.sh
#!/bin/bash
echo STABLE_BUILD_PRINTY_LABEL $(git describe --always --match "v[0-9].*" --dirty)

Printy: add gen_version

genrule(
    name = "gen_version",
    stamp = 1,
    cmd = "echo $$(cat bazel-out/stable-status.txt | grep PRINTY | cut -d ' ' -f 2) > $@",
    outs = ["gen_version.txt"],
)
$ bazel build gen_version
  bazel-genfiles/gen_version.txt
INFO: (11-12 07:54:32.844) Elapsed time: 0.118s, Critical Path: 0.01s
$ cat bazel-genfiles/gen_version.txt
16c272b-dirty

Add genrule printy_stamped to Printy

genrule(
    name = "printy_stamped",
    srcs = [":printy_deploy.jar"],
    tools = [":gen_version.txt"],
    cmd = " && ".join([
        "r=$$PWD",
        "t=$$(mktemp -d)",
        "GEN_VERSION=$$(cat $(location :gen_version.txt))",
        "cd $$t",
        "unzip -q $$r/$<",
        "echo \"Implementation-Version: $$GEN_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF",
        "zip -qr $$r/$@ ."]),
    outs = ["printy_stamped.jar"],
)

Test printy_stamped

$ bazel build printy_stamped
INFO: (11-12 07:59:04.935) Found 1 target...
Target //:printy_stamped up-to-date:
  bazel-genfiles/printy_stamped.jar
INFO: (11-12 07:59:04.959) Elapsed time: 0.104s, Critical Path: 0.01s
$ java -jar bazel-genfiles/printy_stamped.jar --version
16c272b-dirty

Printy: Have you ever looked at your build?

img/printy.png

External Dependencies: Workspace Rules

Bazlets for standalone plugin build

Standalone plugin build

Summary

$ bazel build release
$ tools/maven/api.sh <install|deploy> bazel

Thanks

Thank you

David Ostrovsky

Maintainer, Gerrit Code Review