Improve your Java builds with Buck
31 October 2013
Shawn Pearce
Maintainer, Gerrit Code Review
Shawn Pearce
Maintainer, Gerrit Code Review
package org.eclipsecon.europe2013; import com.google.common.base.Joiner; public class Printy { public static void main(String[] argv) { System.out.println(Joiner.on(' ').join(argv)); } }
Prints command line arguments.
Uses Guava (an external library) to join strings.
Simple.
SRC=src/main/java OUT=build DST=dist LIB=guava.jar SRCS := $(shell find $(SRC) -name '*.java') all: $(DST)/printy_lib.jar $(DST)/printy_lib.jar: compile mkdir -p $(DST) jar cf $@ -C $(DST) . compile: $(SRCS) mkdir -p $(OUT) javac -d $(OUT) -classpath $(LIB) $< clean: rm -rf $(OUT) $(DST)
Thank $DIETY we have Ant.
<project name="Printy" basedir="."> <property name="src" location="src/main/java"/> <property name="out" location="build"/> <property name="dst" location="dist"/> <target name="init"> <mkdir dir="${out}"/> <mkdir dir="${dst}"/> </target> <target name="package" depends="init"> <javac srcdir="${src}" destdir="${out}"> <classpath> <pathelement location="guava.jar"/> </classpath> </javac> <jar jarfile="${dst}/printy_lib.jar" basedir="${out}"/> </target> <target name="clean"> <delete dir="${out}"/> <delete dir="${dst}"/> </target> </project>
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.eclipsecon.europe2013</groupId> <artifactId>Printy</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>15.0</version> </dependency> </dependencies> </project>
Thank $DIETY we no longer have Ant.
java_library( name = 'printy_lib', srcs = glob(['src/main/java/**/*.java']), deps = [':guava'], ) prebuilt_jar( name = 'guava', binary_jar = 'guava.jar', )
Thank $DIETY we no longer need to use Maven.
java_library( name = 'printy_lib', srcs = glob(['src/main/java/**/*.java']), deps = [':guava'], ) prebuilt_jar( name = 'guava', binary_jar = 'guava.jar', ) java_binary( name = 'printy', main_class = 'org.eclipsecon.europe2013.Printy', deps = [':printy_lib'], )
No contest.
Sorry Maven Shade Plugin, you lose.
java_library( name = 'printy_lib', srcs = glob(['src/main/java/**/*.java']), deps = [':guava'], ) prebuilt_jar( name = 'guava', binary_jar = 'guava.jar', )
Clean build
mvn package -Dmaven.{javadoc,test}.skip=true ... 6m50s buck build :gerrit ... 2m 3s buck test --all ... 2m 5s
No-op incremental rebuild
mvn package -Dmaven.{javadoc,test}.skip=true ... 4m44s buck build :gerrit ... 2s
Buck is FAST
Even faster with buckd running in the background
~/buck/bin/buckd time buck :gerrit ... 0.5s
make
-j N
Ant, Maven
Buck
make, Ant, Maven
IF mtime(src) > mtime(out) THEN REBUILD
Buck
IF sha1(src.content) != sha1(out.src.content) THEN REBUILD
Buck is significantly faster while providing perfect accuracy.
make, Ant
Maven
mvn clean
and build again.Buck
make, Ant
Maven
Buck
make
Ant
Maven
Buck
genrule()
can execute Java, Python or shell to create an artifact.include_defs()
loads Python functions to do anything.java_test( name = 'tests', srcs = ['ClientTest.java', 'WindowTest.java'], deps = [':client', '//lib:junit'], source_under_test = [':client'], )
java_test(name = 'ClientTest', srcs = ['ClientTest.java'], deps = [':client', '//lib:junit']) java_test(name = 'WindowTest', srcs = ['WindowTest.java'], deps = [':client', '//lib:junit'])
Python functions to shorten build files
def parallel_test(srcs, deps = []): """Defines one java_test rule per source file.""" for s in srcs: n = s[:s.index('.java')] java_test(name = n, srcs = [s], deps = deps)
Use macros in BUCK files
include_defs('//tools/parallel_test.defs') parallel_test( srcs = glob(['*Test.java']), deps = [':client', ':junit'], )
java_library(name='window', srcs=['Window.java']) java_library(name='client', srcs=['Client.java'], deps=[':window']) public class Window { public int getHeight() { return 500; } } public class Client { public void display(Window w) { System.out.println(w.getHeight()); } }
Change getHeight to return 800 instead of 500?
java_library(name='window', srcs=['Window.java']) java_library(name='client', srcs=['Client.java'], deps=[':window']) public class Window { public int getHeight() { return 500; } } public class Client { public void display(Window w) { System.out.println(w.getHeight()); } }
Change getHeight to return 800 instead of 500?
client does not rebuild
No API changed, only implementation.
Implementation changes do not impact the compliation of client.
java_library(name='window', srcs=['Window.java']) java_library(name='client', srcs=['Client.java'], deps=[':window']) public interface Window { public int getHeight(); } public class Client { public void display(Window w) { System.out.println(w.getHeight()); } }
Change Window from class to interface?
java_library(name='window', srcs=['Window.java']) java_library(name='client', srcs=['Client.java'], deps=[':window']) public interface Window { public int getHeight(); } public class Client { public void display(Window w) { System.out.println(w.getHeight()); } }
Change Window from class to interface?
client rebuilds (Java bytecode is different to call an interface.)
Buck rebuilds modules only when API changes.
class Main { public static void main(String[] a) { new App(); } } class App {} $ javac Main.java
Java compiler implicitly builds App.java from same directory.
java_library(name = 'app', srcs = ['App.java']) java_library(name = 'main', srcs = ['Main.java']) class Main { public static void main(String[] a) { new App(); } } class App {}
Attempting to build main
fails:
$ buck build :main [-] PARSING BUILD FILES...FINISHED 0.2s [+] BUILDING...0.7s |=> //:main... 0.7s (running javac[0.6s]) /Users/sop/git/talks/2013/ec-buck/h2h/fail/Main.java:1: error: cannot find symbol class Main { public static void main(String[] a) { new App(); } } ^ symbol: class App location: class Main BUILD FAILED: //:main failed on step "javac" with exit code 1
Buck isolates library builds, even from the same source directory.
Permits fine grained segmentation within a package.
java_library( name = 'this-module', srcs = glob(['src/main/java/**/*.java']), resources = glob(['src/main/resources/**/*']), deps = [ '//other-module:other-module', '//lib:gson', '//lib:gauva', ], visibility = ['PUBLIC'], )
lib/BUCK
enumerated all prebuilt_jar() from Maven Centrallib/BUCK
include_defs('//lib/maven.defs') maven_jar( name = 'gson', id = 'com.google.code.gson:gson:2.1', sha1 = '2e66da15851f9f5b5079228f856c2f090ba98c38', license = 'Apache2.0', ) maven_jar( name = 'guava', id = 'com.google.guava:guava:14.0', sha1 = '67b7be4ee7ba48e4828a42d6d5069761186d4a53', license = 'Apache2.0', )