diff options
author | 2015-03-03 14:48:59 +0000 | |
---|---|---|
committer | 2015-03-05 14:27:30 +0000 | |
commit | 825603cfb305e54ca44cf4b4d2dc3350ca9eb210 (patch) | |
tree | 24370331d25fcc4738045d6b1aba2a33a84a1060 /src/java_tools/buildjar/java | |
parent | 619e86b84b60e34c317f20d4ad237d5af58bc07b (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')
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); + } +} |