aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/java_tools/buildjar/BUILD134
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavaCompiler.java249
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavac.java28
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfig.java31
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfigGenerator.java62
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaLangtools.java58
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavacBootclasspath.java89
-rw-r--r--src/java_tools/buildjar/javatests/com/google/devtools/build/java/bazel/BazelJavaCompilerTest.java118
-rw-r--r--src/main/java/BUILD2
9 files changed, 769 insertions, 2 deletions
diff --git a/src/java_tools/buildjar/BUILD b/src/java_tools/buildjar/BUILD
index 8b61435c98..d467e66bf9 100644
--- a/src/java_tools/buildjar/BUILD
+++ b/src/java_tools/buildjar/BUILD
@@ -2,7 +2,7 @@ package(default_visibility = ["//src:__pkg__"])
java_binary(
name = "JavaBuilder",
- srcs = glob(["java/**/*.java"]),
+ srcs = glob(["java/com/google/devtools/build/buildjar/**/*.java"]),
main_class = "com.google.devtools.build.buildjar.BazelJavaBuilder",
deps = [
"//src/main/protobuf:proto_deps",
@@ -12,3 +12,135 @@ java_binary(
"//tools/jdk:langtools-neverlink",
],
)
+
+# Exports $(locations //tools/defaults:javac_bootclasspath) to the java world.
+genrule(
+ name = "javac-bootclasspath-locations",
+ srcs = ["//tools/defaults:javac_bootclasspath"],
+ outs = ["java/com/google/devtools/build/java/bazel/JavacBootclasspathLocations.java"],
+ cmd = """
+declare -a paths=($(SRCS)) && paths=($${paths[@]#$(GENDIR)/}) &&
+IFS=: &&
+cat > $@ <<EOF
+package com.google.devtools.build.java.bazel;
+public class JavacBootclasspathLocations {
+ public static final String BOOTCLASSPATH = "$${paths[*]}";
+}
+EOF
+""",
+)
+
+# Provides java-level access to //tools/defaults:javac_bootclasspath
+# as defined by blaze's --javac_bootclasspath flag
+java_library(
+ name = "JavacBootclasspath",
+ srcs = [
+ "java/com/google/devtools/build/java/bazel/JavacBootclasspath.java",
+ ":javac-bootclasspath-locations",
+ ],
+ data = ["//tools/defaults:javac_bootclasspath"],
+ visibility = ["//visibility:public"],
+)
+
+# Exports $(location //tools/defaults:java_langtools) to the java world.
+genrule(
+ name = "java-langtools-location",
+ srcs = ["//tools/defaults:java_langtools"],
+ outs = ["java/com/google/devtools/build/java/bazel/JavaLangtoolsLocation.java"],
+ cmd = """
+path=$(SRCS) && path=$${path#$(GENDIR)/} &&
+cat > $@ <<EOF
+package com.google.devtools.build.java.bazel;
+public class JavaLangtoolsLocation {
+ public static final String FILE = "$${path}";
+}
+EOF
+""",
+)
+
+# Provides java-level access to //tools/defaults:java_langtools
+# as defined by bazel's --java_langtools flag
+java_library(
+ name = "JavaLangtools",
+ srcs = [
+ "java/com/google/devtools/build/java/bazel/JavaLangtools.java",
+ ":java-langtools-location",
+ ],
+ data = ["//tools/defaults:java_langtools"],
+ visibility = ["//visibility:public"],
+)
+
+# Utility to export javacopts to the java world from the default javabuilder (a
+# java_binary deploy jar).
+java_binary(
+ name = "JavaBuilderConfigGenerator",
+ srcs = ["java/com/google/devtools/build/java/bazel/JavaBuilderConfigGenerator.java"],
+ main_class = "com.google.devtools.build.java.bazel.JavaBuilderConfigGenerator",
+ deps = [
+ "//src/main/java:java-toolchain-parser",
+ "//third_party:guava",
+ ],
+)
+
+# Exports the default javacopts to the java world from the default javabuilder
+# (a java_binary deploy jar).
+# We could put any java target as scope and in deps here.
+genquery(
+ name = "java_toolchain_content",
+ expression = "kind(java_toolchain, deps(//tools/defaults:java_toolchain))",
+ opts = ["--output=proto"],
+ scope = ["//tools/defaults:java_toolchain"],
+)
+
+genrule(
+ name = "javabuilder-javacopts",
+ srcs = [":java_toolchain_content"],
+ outs = ["java/com/google/devtools/build/java/bazel/JavaBuilderJavacOpts.java"],
+ cmd = "$(location :JavaBuilderConfigGenerator) $< > $@",
+ tools = [":JavaBuilderConfigGenerator"],
+)
+
+# Provides java-level access to the default javacopts in the current
+# JavaBuilder release as defined by bazel's --javabuilder_top flag
+java_library(
+ name = "JavaBuilderConfig",
+ srcs = [
+ "java/com/google/devtools/build/java/bazel/JavaBuilderConfig.java",
+ ":javabuilder-javacopts",
+ ],
+ data = ["//tools/defaults:javabuilder"],
+)
+
+# Provides programmatic access to a bazel compatible javac.
+# Use this instead of ToolProvider.getSystemJavaCompiler().
+java_library(
+ name = "BazelJavaCompiler",
+ srcs = ["java/com/google/devtools/build/java/bazel/BazelJavaCompiler.java"],
+ data = ["//tools/defaults:java_langtools"],
+ deps = [
+ ":JavaBuilderConfig",
+ ":JavaLangtools",
+ ":JavacBootclasspath",
+ ],
+)
+
+# Command line version of BazelJavaCompiler, interface-compatible
+# with the javac command, for use with ant, for example.
+java_library(
+ name = "BazelJavac",
+ srcs = ["java/com/google/devtools/build/java/bazel/BazelJavac.java"],
+ deps = [":BazelJavaCompiler"],
+)
+
+java_test(
+ name = "BazelJavaCompilerTest",
+ size = "small",
+ srcs = ["javatests/com/google/devtools/build/java/bazel/BazelJavaCompilerTest.java"],
+ args = ["com.google.devtools.build.java.bazel.BazelJavaCompilerTest"],
+ deps = [
+ ":BazelJavaCompiler",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavaCompiler.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavaCompiler.java
new file mode 100644
index 0000000000..743693406e
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavaCompiler.java
@@ -0,0 +1,249 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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.devtools.build.java.bazel;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.lang.model.SourceVersion;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+
+/**
+ * Provides a {@link JavaCompiler} that has behavior as similar as possible
+ * to the java compiler provided by default by Bazel.
+ * Replace calls to {@link javax.tools.ToolProvider#getSystemJavaCompiler}
+ * with calls to {@link BazelJavaCompiler#newInstance}.
+ *
+ * <p>This class is typically used only from a host build tool or in tests.
+ * When using this in production, langtools.jar and the bootclasspath jars
+ * are deployed as separate jar files within the runfiles directory.
+ */
+public class BazelJavaCompiler {
+
+ // The default blessed javac options.
+
+ private static final String DEFAULT_BOOTCLASSPATH = JavacBootclasspath.asString();
+
+ private static final String[] DEFAULT_JAVACOPTS;
+ static {
+ List<String> defaultJavacopts = new ArrayList<>(JavaBuilderConfig.defaultJavacOpts());
+
+ // The bootclasspath must be specified both via an invocation option and
+ // via fileManager.setLocation(PLATFORM_CLASS_PATH), to work around what
+ // appears to be a bug in jdk[6,8] javac.
+ defaultJavacopts.addAll(Arrays.asList("-bootclasspath", DEFAULT_BOOTCLASSPATH));
+
+ DEFAULT_JAVACOPTS = defaultJavacopts.toArray(new String[defaultJavacopts.size()]);
+ }
+
+ private static final Class<? extends JavaCompiler> JAVA_COMPILER_CLASS = getJavaCompilerClass();
+
+ private static class LangtoolsClassLoader extends URLClassLoader {
+
+ public LangtoolsClassLoader() throws MalformedURLException {
+ super(new URL[] { getLangtoolsJar().toURI().toURL() },
+ // We use the bootstrap classloader (null) as the parent classloader
+ // instead of the default "system" class loader; we intentionally do
+ // not consult the classpath. This way the class path is not
+ // polluted, we reduce the risk of having multiple langtools on the
+ // classpath, and langtools.jar is only opened if this method is
+ // called. And this will reduce problems for automated java
+ // dependency analysis, which other teams are trying to do.
+ null);
+ }
+ }
+
+ private static Class<? extends JavaCompiler> getJavaCompilerClass() {
+ try {
+ ClassLoader cl = new LangtoolsClassLoader();
+ return getJavaCompilerClass(cl);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot get java compiler", e);
+ }
+ }
+
+ private static Class<? extends JavaCompiler> getJavaCompilerClass(ClassLoader cl)
+ throws Exception {
+ return Class.forName("com.sun.tools.javac.api.JavacTool", true, cl)
+ .asSubclass(JavaCompiler.class);
+ }
+
+ /**
+ * Returns the langtools jar.
+ */
+ public static File getLangtoolsJar() {
+ return JavaLangtools.file();
+ }
+
+ /**
+ * Returns the default javacopts, including the blessed bootclasspath.
+ */
+ public static List<String> getDefaultJavacopts() {
+ return new ArrayList<>(Arrays.asList(DEFAULT_JAVACOPTS));
+ }
+
+ /**
+ * Returns a new {@link JavaCompiler} that has behavior as similar as
+ * possible to the java compiler provided by default by the bazel build
+ * system, independent of the user-specified {@code JAVABASE}.
+ *
+ * <p>More precisely, this method works analogously to {@link
+ * javax.tools.ToolProvider#getSystemJavaCompiler}, but returns a {@code
+ * JavaCompiler} that differs in these details:
+ *
+ * <ul>
+ *
+ * <li> uses the blessed javac implementation: {@code //tools/defaults:java_langtools},
+ * as defined by bazel's --java_langtools flag.
+ *
+ * <li> uses the blessed boot class path: {@code //tools/defaults:javac_bootclasspath},
+ * as defined by bazel's --javac_bootclasspath flag.
+ *
+ * <li> uses the blessed default values for javac options such as {@code -source}
+ *
+ * </ul>
+ *
+ * <p>This class ensures that (by default) the {@code -source}, {@code
+ * -target} and {@code -bootclasspath} flags all agree and specify the same
+ * (blessed) JDK version, for language and API compatibility.
+ *
+ * <p>This method finds the javac implementation using a custom classloader
+ * that does not consult the user's classpath. This works well, unless the
+ * return value is cast to a javac-implementation class like {@code
+ * JavacTask}, in which case the dreaded classloader error "can't cast
+ * JavacTaskImpl to JavacTask" raises its ugly head, in which case you should
+ * use {@link #newInstance(ClassLoader)} instead.
+ */
+ public static JavaCompiler newInstance() {
+ try {
+ return newInstance(JAVA_COMPILER_CLASS.newInstance());
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot get java compiler", e);
+ }
+ }
+
+ /**
+ * Returns a new {@link JavaCompiler} that has behavior as similar as
+ * possible to the java compiler provided by default by bazel,
+ * independent of the user-specified {@code JAVABASE}.
+ *
+ * <p>This method has effect identical to {@link #newInstance()} (and that
+ * method is generally preferred to this one), except that the javac
+ * implementation is found via the provided classloader instead of defining a
+ * custom classloader that knows the standard location of the blessed javac
+ * implementation.
+ *
+ * <p>This method is needed when the return value is cast to a
+ * javac-implementation class like {@code JavacTask}, to avoid the dreaded
+ * multiple classloader error "can't cast JavacTaskImpl to JavacTask".
+ *
+ * <p>Typically, users should pass {@code ClassLoader.getSystemClassLoader()}
+ * as the argument to this method.
+ */
+ public static JavaCompiler newInstance(ClassLoader cl) {
+ try {
+ return newInstance(getJavaCompilerClass(cl).newInstance());
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot get java compiler", e);
+ }
+ }
+
+ private static JavaCompiler newInstance(final JavaCompiler delegate) {
+ // We forward most operations to the JavaCompiler implementation in langtools.jar.
+ return new JavaCompiler() {
+ @Override
+ public CompilationTask getTask(
+ Writer out,
+ JavaFileManager fileManager,
+ DiagnosticListener<? super JavaFileObject> diagnosticListener,
+ Iterable<String> options,
+ Iterable<String> classes,
+ Iterable<? extends JavaFileObject> compilationUnits) {
+ // We prepend bazel's default javacopts to user javacopts,
+ // so that the user can override them. javac supports this
+ // "last option wins" style of option override.
+ List<String> fullOptions = getDefaultJavacopts();
+ if (options != null) {
+ for (String option : options) {
+ fullOptions.add(option);
+ }
+ }
+ return delegate.getTask(out,
+ fileManager,
+ diagnosticListener,
+ fullOptions,
+ classes,
+ compilationUnits);
+ }
+
+ @Override
+ public StandardJavaFileManager getStandardFileManager(
+ DiagnosticListener<? super JavaFileObject> diagnosticListener,
+ Locale locale,
+ Charset charset) {
+ StandardJavaFileManager fileManager = delegate.getStandardFileManager(
+ diagnosticListener,
+ locale,
+ charset);
+
+ try {
+ fileManager.setLocation(
+ StandardLocation.PLATFORM_CLASS_PATH, // bootclasspath
+ JavacBootclasspath.asFiles());
+ } catch (IOException e) {
+ // Should never happen, according to javadocs for setLocation
+ throw new RuntimeException(e);
+ }
+ return fileManager;
+ }
+
+ @Override
+ public int run(InputStream in, OutputStream out, OutputStream err,
+ String... arguments) {
+ // prepend bazel's default javacopts to user arguments
+ List<String> args = getDefaultJavacopts();
+ args.addAll(Arrays.asList(arguments));
+ return delegate.run(in, out, err, args.toArray(new String[0]));
+ }
+
+ @Override
+ public Set<SourceVersion> getSourceVersions() {
+ return delegate.getSourceVersions();
+ }
+
+ @Override
+ public int isSupportedOption(String option) {
+ return delegate.isSupportedOption(option);
+ }
+ };
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavac.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavac.java
new file mode 100644
index 0000000000..c9318937c1
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/BazelJavac.java
@@ -0,0 +1,28 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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.devtools.build.java.bazel;
+
+import javax.tools.JavaCompiler;
+
+/**
+ * Command line version of {@link BazelJavaCompiler}, interface-compatible
+ * with the {@code javac} command, for use with {@code ant}, for example.
+ */
+public class BazelJavac {
+ public static void main(String[] args) {
+ JavaCompiler compiler = BazelJavaCompiler.newInstance();
+ System.exit(compiler.run(System.in, System.out, System.err, args));
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfig.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfig.java
new file mode 100644
index 0000000000..32660b7ab5
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfig.java
@@ -0,0 +1,31 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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.devtools.build.java.bazel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class to provide java-level access to the blessed javabuilder javacopts.
+ */
+public class JavaBuilderConfig {
+
+ public static List<String> defaultJavacOpts() {
+ List<String> result = new ArrayList<>();
+ Collections.addAll(result, JavaBuilderJavacOpts.DEFAULT_JAVACOPTS);
+ return result;
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfigGenerator.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfigGenerator.java
new file mode 100644
index 0000000000..3a1415d83d
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaBuilderConfigGenerator.java
@@ -0,0 +1,62 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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.devtools.build.java.bazel;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.rules.java.JavaToolchainData;
+import com.google.devtools.build.lib.rules.java.JavaToolchainDataParser;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Utility class to generate {@link JavaBuilderConfig}.
+ */
+public class JavaBuilderConfigGenerator {
+
+ private static void die(String message) {
+ System.err.println(message);
+ System.err.println("\nThis program is expecting the protocol buffer output of a bazel query");
+ System.err.println("containing exactly one java_toolchain target. An example of such output");
+ System.err.println("can be obtained by:");
+ System.err.println("\bazel query --output=proto "
+ + "'kind(java_toolchain, deps(//tools/defaults:java_toolchain))'");
+ System.exit(-1);
+ }
+
+ public static void main(String[] args) {
+ try {
+ InputStream stream = System.in;
+ if (args.length > 0) {
+ stream = new FileInputStream(args[0]);
+ }
+ ImmutableMap<String, JavaToolchainData> data = JavaToolchainDataParser.parse(stream);
+ if (data.size() != 1) {
+ die(data.isEmpty() ? "No java_toolchain found!" : "Multiple java_toolchain found!");
+ }
+ JavaToolchainData first = data.values().asList().get(0);
+ String optsString = Joiner.on("\", \"").join(first.getJavacOptions());
+ System.out.println("package com.google.devtools.build.java.bazel;");
+ System.out.println("public class JavaBuilderJavacOpts {");
+ System.out.println("public static final String[] DEFAULT_JAVACOPTS = {\""
+ + optsString + "\"};");
+ System.out.println("}");
+ } catch (IOException e) {
+ die("Cannot load input file: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaLangtools.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaLangtools.java
new file mode 100644
index 0000000000..08de5af13c
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavaLangtools.java
@@ -0,0 +1,58 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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.devtools.build.java.bazel;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOError;
+import java.nio.file.AccessDeniedException;
+
+/**
+ * Utility class to provide java-level access to the blessed langtools jar path:
+ * {@code //third_party/java/jdk:langtools}, as defined by bazel's --java_langtools flag.
+ */
+public class JavaLangtools {
+
+ private static final File FILE;
+
+ private static String getRunfilesDir() {
+ String dir = System.getenv("JAVA_RUNFILES");
+ if (dir == null) {
+ dir = System.getenv("TEST_SRCDIR");
+ }
+ if (dir == null) {
+ throw new IllegalStateException(
+ "Neither JAVA_RUNFILES nor TEST_SRCDIR environment variable was defined!");
+ }
+ return dir;
+ }
+
+ static {
+ File file = new File(getRunfilesDir(), JavaLangtoolsLocation.FILE);
+ if (!file.isFile()) {
+ throw new IOError(new FileNotFoundException("Can't find langtools jar: " + file.getPath()));
+ } else if (!file.canRead()) {
+ throw new IOError(new AccessDeniedException("Can't read langtools jar: " + file.getPath()));
+ }
+ FILE = file;
+ }
+
+ /**
+ * Returns the blessed langtools jar path.
+ */
+ public static File file() {
+ return FILE;
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavacBootclasspath.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavacBootclasspath.java
new file mode 100644
index 0000000000..a7f7f458a5
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/bazel/JavacBootclasspath.java
@@ -0,0 +1,89 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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.devtools.build.java.bazel;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOError;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class to provide java-level access to the blessed javac boot class path:
+ * {@code //tools/defaults:javac_bootclasspath}, as defined by bazel's --javac_bootclasspath flag.
+ *
+ * <p>This class is typically used only from a host build tool or in
+ * tests. When using this in production, the bootclasspath is
+ * deployed as separate jar files within the runfiles directory.
+ */
+public class JavacBootclasspath {
+
+ private static final List<File> AS_FILES;
+ private static final String AS_STRING;
+
+ private static String getRunfilesDir() {
+ String dir = System.getenv("JAVA_RUNFILES");
+ if (dir == null) {
+ dir = System.getenv("TEST_SRCDIR");
+ }
+ if (dir == null) {
+ throw new IllegalStateException(
+ "Neither JAVA_RUNFILES nor TEST_SRCDIR environment variable was defined!");
+ }
+ return dir;
+ }
+
+ static {
+ String[] locations = JavacBootclasspathLocations.BOOTCLASSPATH.split(":");
+ String runfilesRoot = getRunfilesDir();
+ List<File> files = new ArrayList<>(locations.length);
+ StringBuilder str = new StringBuilder();
+ for (String location : locations) {
+ File file = new File(runfilesRoot, location);
+ if (!file.isFile()) {
+ throw new IOError(
+ new FileNotFoundException(
+ "Can't find boot class path element: " + file.getPath()));
+ }
+ files.add(file);
+ if (str.length() > 0) {
+ str.append(':');
+ }
+ str.append(file.getAbsolutePath());
+ }
+ AS_FILES = files;
+ AS_STRING = str.toString();
+ }
+
+ /**
+ * Returns the blessed boot class path as a colon-separated string.
+ *
+ * Suitable for passing as the value of a {@code -bootclasspath} flag.
+ * Valid while the current build action or test is executing.
+ */
+ public static String asString() {
+ return AS_STRING;
+ }
+
+ /**
+ * Returns the blessed boot class path as a list of {@code File} objects.
+ *
+ * Each {@code File} will represent a jar file that will exist while the
+ * current build action or test is executing.
+ */
+ public static List<File> asFiles() {
+ return new ArrayList<>(AS_FILES);
+ }
+}
diff --git a/src/java_tools/buildjar/javatests/com/google/devtools/build/java/bazel/BazelJavaCompilerTest.java b/src/java_tools/buildjar/javatests/com/google/devtools/build/java/bazel/BazelJavaCompilerTest.java
new file mode 100644
index 0000000000..9c6c1c8a82
--- /dev/null
+++ b/src/java_tools/buildjar/javatests/com/google/devtools/build/java/bazel/BazelJavaCompilerTest.java
@@ -0,0 +1,118 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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.devtools.build.java.bazel;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+
+/**
+ * Sanity checks: make sure we can instantiate a working javac compiler.
+ */
+@RunWith(JUnit4.class)
+public class BazelJavaCompilerTest {
+
+ private static File getTmpDir() {
+ String tmpdir = System.getenv("TEST_TMPDIR");
+ if (tmpdir == null) {
+ // Fall back on the system temporary directory
+ tmpdir = System.getProperty("java.io.tmpdir");
+ }
+ if (tmpdir == null) {
+ fail("TEST_TMPDIR environment variable is not set!");
+ }
+ return new File(tmpdir);
+ }
+
+ @Test
+ public void testCompilerNewInstance() throws Exception {
+ JavaCompiler javac = BazelJavaCompiler.newInstance();
+
+ assertNotNull(javac.getStandardFileManager(null, null, null));
+
+ // This is a simplified pattern of invoking the compiler API. Note, however, that
+ // many examples cast to JavacTask or JavacTaskImpl and invoke the phases separately.
+ // Currently, either cast will fail with something that looks like classloader issues:
+ // "com.sun.tools.javac.api.JavacTask cannot be cast to com.sun.tools.javac.api.JavacTask"
+ assertNotNull(javac.getTask(null, null, null, null, null, null));
+ }
+
+ @Test
+ public void testAllowsJava7LanguageFeatures() throws Exception {
+ assertCompileSucceeds("string://Test.java",
+ "class Test {"
+ + " void foo(String s) {"
+ + " switch (s) {"
+ + " default:"
+ + " return;"
+ + " }"
+ + " }"
+ + "}");
+ }
+
+ @Test
+ public void testAllowsJava7APIs() throws Exception {
+ assertCompileSucceeds("string://Test.java",
+ "import java.nio.file.Files;"
+ + "class Test {}");
+ }
+
+ @Test
+ public void testJavacOpts() throws Exception {
+ // BazelJavaCompiler loads the default opts from JavaBuilder, so testing against
+ // the exact options would be brittle. This is a basic sanity check that
+ // the default options include the correct -encoding (which is loaded from
+ // JavaBuilder), and that BazelJavaCompiler is appending the -bootclasspath.
+ List<String> opts = BazelJavaCompiler.getDefaultJavacopts();
+ assertTrue(opts.contains("UTF-8"));
+ assertTrue(opts.contains("-bootclasspath"));
+ }
+
+ private void assertCompileSucceeds(final String uri, final String content) throws Exception {
+ JavaCompiler javac = BazelJavaCompiler.newInstance();
+ JavaFileObject source = new SimpleJavaFileObject(
+ URI.create(uri), JavaFileObject.Kind.SOURCE) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return content;
+ }
+ };
+ StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null);
+ // setting the output path by passing a flag to getTask is not reliable
+ fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(getTmpDir()));
+ DiagnosticCollector<JavaFileObject> messages = new DiagnosticCollector<>();
+ JavaCompiler.CompilationTask task = javac.getTask(
+ null, fileManager, messages, null, null, Collections.singletonList(source));
+ assertTrue(task.call());
+ assertTrue(messages.getDiagnostics().isEmpty());
+ }
+}
diff --git a/src/main/java/BUILD b/src/main/java/BUILD
index 65a585a49e..49989899d8 100644
--- a/src/main/java/BUILD
+++ b/src/main/java/BUILD
@@ -62,7 +62,7 @@ java_library(
"com/google/devtools/build/lib/Constants.java",
]),
visibility = [
- "//src/test:__subpackages__",
+ "//src/java_tools/buildjar:__pkg__",
],
deps = [
"//src/main/protobuf:proto_build",