aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/buildjar/java
diff options
context:
space:
mode:
authorGravatar Damien Martin-Guillerez <dmarting@google.com>2015-03-03 14:48:59 +0000
committerGravatar Ulf Adams <ulfjack@google.com>2015-03-05 14:27:30 +0000
commit825603cfb305e54ca44cf4b4d2dc3350ca9eb210 (patch)
tree24370331d25fcc4738045d6b1aba2a33a84a1060 /src/java_tools/buildjar/java
parent619e86b84b60e34c317f20d4ad237d5af58bc07b (diff)
Open-sourcing BazelJavaCompiler.
This is a mock for java compilation. It can be used to do tests using the same configuration as when Bazel invoke javac. -- MOS_MIGRATED_REVID=87608177
Diffstat (limited to 'src/java_tools/buildjar/java')
-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
6 files changed, 517 insertions, 0 deletions
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);
+ }
+}