| // Copyright 2016 The Bazel Authors. 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.lib.rules.java; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.devtools.build.lib.rules.java.JavaCompileActionBuilder.UTF8_ENVIRONMENT; |
| import static java.nio.charset.StandardCharsets.ISO_8859_1; |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.ParamFileInfo; |
| import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; |
| import com.google.devtools.build.lib.analysis.actions.SpawnAction; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider.JavaPluginInfo; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.lib.util.LazyString; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Action builder for Java header compilation, to be used if --java_header_compilation is enabled. |
| * |
| * <p>The header compiler consumes the inputs of a java compilation, and produces an interface jar |
| * that can be used as a compile-time jar by upstream targets. The header interface jar is |
| * equivalent to the output of ijar, but unlike ijar the header compiler operates directly on Java |
| * source files instead post-processing the class outputs of the compilation. Compiling the |
| * interface jar from source moves javac off the build's critical path. |
| * |
| * <p>The implementation of the header compiler tool can be found under {@code |
| * //src/java_tools/buildjar/java/com/google/devtools/build/java/turbine}. |
| */ |
| public class JavaHeaderCompileActionBuilder { |
| |
| private final RuleContext ruleContext; |
| |
| private Artifact outputJar; |
| @Nullable private Artifact outputDepsProto; |
| private ImmutableSet<Artifact> sourceFiles = ImmutableSet.of(); |
| private final Collection<Artifact> sourceJars = new ArrayList<>(); |
| private NestedSet<Artifact> classpathEntries = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER); |
| private ImmutableList<Artifact> bootclasspathEntries = ImmutableList.of(); |
| @Nullable private Label targetLabel; |
| @Nullable private String injectingRuleKind; |
| private PathFragment tempDirectory; |
| private BuildConfiguration.StrictDepsMode strictJavaDeps = BuildConfiguration.StrictDepsMode.OFF; |
| private boolean reduceClasspath = true; |
| private NestedSet<Artifact> directJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER); |
| private NestedSet<Artifact> compileTimeDependencyArtifacts = |
| NestedSetBuilder.emptySet(Order.STABLE_ORDER); |
| private ImmutableList<String> javacOpts; |
| private JavaPluginInfo plugins = JavaPluginInfo.empty(); |
| |
| private NestedSet<Artifact> additionalInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER); |
| private Artifact javacJar; |
| private NestedSet<Artifact> toolsJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER); |
| |
| public JavaHeaderCompileActionBuilder(RuleContext ruleContext) { |
| this.ruleContext = ruleContext; |
| } |
| |
| /** Sets the output jdeps file. */ |
| public JavaHeaderCompileActionBuilder setOutputDepsProto(@Nullable Artifact outputDepsProto) { |
| this.outputDepsProto = outputDepsProto; |
| return this; |
| } |
| |
| /** Sets the direct dependency artifacts. */ |
| public JavaHeaderCompileActionBuilder setDirectJars(NestedSet<Artifact> directJars) { |
| checkNotNull(directJars, "directJars must not be null"); |
| this.directJars = directJars; |
| return this; |
| } |
| |
| /** Sets the .jdeps artifacts for direct dependencies. */ |
| public JavaHeaderCompileActionBuilder setCompileTimeDependencyArtifacts( |
| NestedSet<Artifact> dependencyArtifacts) { |
| checkNotNull(dependencyArtifacts, "dependencyArtifacts must not be null"); |
| this.compileTimeDependencyArtifacts = dependencyArtifacts; |
| return this; |
| } |
| |
| /** Sets Java compiler flags. */ |
| public JavaHeaderCompileActionBuilder setJavacOpts(ImmutableList<String> javacOpts) { |
| checkNotNull(javacOpts, "javacOpts must not be null"); |
| this.javacOpts = javacOpts; |
| return this; |
| } |
| |
| /** Sets the output jar. */ |
| public JavaHeaderCompileActionBuilder setOutputJar(Artifact outputJar) { |
| checkNotNull(outputJar, "outputJar must not be null"); |
| this.outputJar = outputJar; |
| return this; |
| } |
| |
| /** Adds Java source files to compile. */ |
| public JavaHeaderCompileActionBuilder setSourceFiles(ImmutableSet<Artifact> sourceFiles) { |
| checkNotNull(sourceFiles, "sourceFiles must not be null"); |
| this.sourceFiles = sourceFiles; |
| return this; |
| } |
| |
| /** Adds a jar archive of Java sources to compile. */ |
| public JavaHeaderCompileActionBuilder addSourceJars(Collection<Artifact> sourceJars) { |
| checkNotNull(sourceJars, "sourceJars must not be null"); |
| this.sourceJars.addAll(sourceJars); |
| return this; |
| } |
| |
| /** Sets the compilation classpath entries. */ |
| public JavaHeaderCompileActionBuilder setClasspathEntries(NestedSet<Artifact> classpathEntries) { |
| checkNotNull(classpathEntries, "classpathEntries must not be null"); |
| this.classpathEntries = classpathEntries; |
| return this; |
| } |
| |
| /** Sets the compilation bootclasspath entries. */ |
| public JavaHeaderCompileActionBuilder setBootclasspathEntries( |
| ImmutableList<Artifact> bootclasspathEntries) { |
| checkNotNull(bootclasspathEntries, "bootclasspathEntries must not be null"); |
| this.bootclasspathEntries = bootclasspathEntries; |
| return this; |
| } |
| |
| /** Sets the annotation processors classpath entries. */ |
| public JavaHeaderCompileActionBuilder setPlugins(JavaPluginInfo plugins) { |
| checkNotNull(plugins, "plugins must not be null"); |
| checkState(this.plugins.isEmpty()); |
| this.plugins = plugins; |
| return this; |
| } |
| |
| /** Sets the label of the target being compiled. */ |
| public JavaHeaderCompileActionBuilder setTargetLabel(@Nullable Label targetLabel) { |
| this.targetLabel = targetLabel; |
| return this; |
| } |
| |
| /** Sets the injecting rule kind of the target being compiled. */ |
| public JavaHeaderCompileActionBuilder setInjectingRuleKind(@Nullable String injectingRuleKind) { |
| this.injectingRuleKind = injectingRuleKind; |
| return this; |
| } |
| |
| /** |
| * Sets the path to a temporary directory, e.g. for extracting sourcejar entries to before |
| * compilation. |
| */ |
| public JavaHeaderCompileActionBuilder setTempDirectory(PathFragment tempDirectory) { |
| checkNotNull(tempDirectory, "tempDirectory must not be null"); |
| this.tempDirectory = tempDirectory; |
| return this; |
| } |
| |
| /** Sets the Strict Java Deps mode. */ |
| public JavaHeaderCompileActionBuilder setStrictJavaDeps( |
| BuildConfiguration.StrictDepsMode strictJavaDeps) { |
| checkNotNull(strictJavaDeps, "strictJavaDeps must not be null"); |
| this.strictJavaDeps = strictJavaDeps; |
| return this; |
| } |
| |
| /** Enables reduced classpaths. */ |
| public JavaHeaderCompileActionBuilder setReduceClasspath(boolean reduceClasspath) { |
| this.reduceClasspath = reduceClasspath; |
| return this; |
| } |
| |
| /** Sets the javabase inputs. */ |
| public JavaHeaderCompileActionBuilder setAdditionalInputs(NestedSet<Artifact> additionalInputs) { |
| checkNotNull(additionalInputs, "additionalInputs must not be null"); |
| this.additionalInputs = additionalInputs; |
| return this; |
| } |
| |
| /** Sets the javac jar. */ |
| public JavaHeaderCompileActionBuilder setJavacJar(Artifact javacJar) { |
| checkNotNull(javacJar, "javacJar must not be null"); |
| this.javacJar = javacJar; |
| return this; |
| } |
| |
| /** Sets the tools jars. */ |
| public JavaHeaderCompileActionBuilder setToolsJars(NestedSet<Artifact> toolsJars) { |
| checkNotNull(toolsJars, "toolsJars must not be null"); |
| this.toolsJars = toolsJars; |
| return this; |
| } |
| |
| /** Builds and registers the action for a header compilation. */ |
| public void build(JavaToolchainProvider javaToolchain, JavaRuntimeInfo hostJavabase) { |
| checkNotNull(outputDepsProto, "outputDepsProto must not be null"); |
| checkNotNull(sourceFiles, "sourceFiles must not be null"); |
| checkNotNull(sourceJars, "sourceJars must not be null"); |
| checkNotNull(classpathEntries, "classpathEntries must not be null"); |
| checkNotNull(bootclasspathEntries, "bootclasspathEntries must not be null"); |
| checkNotNull(tempDirectory, "tempDirectory must not be null"); |
| checkNotNull(strictJavaDeps, "strictJavaDeps must not be null"); |
| checkNotNull(directJars, "directJars must not be null"); |
| checkNotNull(compileTimeDependencyArtifacts, "compileTimeDependencyArtifacts must not be null"); |
| checkNotNull(javacOpts, "javacOpts must not be null"); |
| |
| // Invariant: if strictJavaDeps is OFF, then directJars and |
| // dependencyArtifacts are ignored |
| if (strictJavaDeps == BuildConfiguration.StrictDepsMode.OFF) { |
| directJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER); |
| compileTimeDependencyArtifacts = NestedSetBuilder.emptySet(Order.STABLE_ORDER); |
| } |
| |
| // The compilation uses API-generating annotation processors and has to fall back to |
| // javac-turbine. |
| boolean requiresAnnotationProcessing = !plugins.isEmpty(); |
| |
| SpawnAction.Builder builder = new SpawnAction.Builder(); |
| |
| builder.setEnvironment( |
| ruleContext.getConfiguration().getActionEnvironment().addFixedVariables(UTF8_ENVIRONMENT)); |
| |
| builder.setProgressMessage( |
| new ProgressMessage( |
| this.outputJar, sourceFiles.size() + sourceJars.size(), plugins.processorClasses())); |
| |
| builder.addTool(javacJar); |
| builder.addTransitiveTools(toolsJars); |
| |
| builder.addOutput(outputJar); |
| builder.addOutput(outputDepsProto); |
| |
| builder.addTransitiveInputs(additionalInputs); |
| builder.addInputs(bootclasspathEntries); |
| builder.addInputs(sourceJars); |
| builder.addInputs(sourceFiles); |
| |
| FilesToRunProvider headerCompiler = |
| (!requiresAnnotationProcessing && javaToolchain.getHeaderCompilerDirect() != null) |
| ? javaToolchain.getHeaderCompilerDirect() |
| : javaToolchain.getHeaderCompiler(); |
| // The header compiler is either a jar file that needs to be executed using |
| // `java -jar <path>`, or an executable that can be run directly. |
| if (!headerCompiler.getExecutable().getExtension().equals("jar")) { |
| builder.setExecutable(headerCompiler); |
| } else { |
| builder.addTransitiveInputs(hostJavabase.javaBaseInputsMiddleman()); |
| builder.setJarExecutable( |
| hostJavabase.javaBinaryExecPath(), |
| headerCompiler.getExecutable(), |
| javaToolchain.getJvmOptions()); |
| } |
| |
| CustomCommandLine.Builder commandLine = |
| CustomCommandLine.builder() |
| .addExecPath("--output", outputJar) |
| .addExecPath("--output_deps", outputDepsProto) |
| .addPath("--temp_dir", tempDirectory) |
| .addExecPaths("--bootclasspath", bootclasspathEntries) |
| .addExecPaths("--sources", sourceFiles) |
| .addExecPaths("--source_jars", sourceJars) |
| .add("--injecting_rule_kind", injectingRuleKind); |
| |
| if (!javacOpts.isEmpty()) { |
| commandLine.addAll("--javacopts", javacOpts); |
| // terminate --javacopts with `--` to support javac flags that start with `--` |
| commandLine.add("--"); |
| } |
| |
| if (targetLabel != null) { |
| commandLine.add("--target_label"); |
| if (targetLabel.getPackageIdentifier().getRepository().isDefault() |
| || targetLabel.getPackageIdentifier().getRepository().isMain()) { |
| commandLine.addLabel(targetLabel); |
| } else { |
| // @-prefixed strings will be assumed to be params filenames and expanded, |
| // so add an extra @ to escape it. |
| commandLine.addPrefixedLabel("@", targetLabel); |
| } |
| } |
| |
| // The action doesn't require annotation processing, so use the non-javac-based turbine |
| // implementation. |
| if (!requiresAnnotationProcessing) { |
| NestedSet<Artifact> classpath; |
| if (!directJars.isEmpty() || classpathEntries.isEmpty()) { |
| classpath = directJars; |
| } else { |
| classpath = classpathEntries; |
| } |
| builder.addTransitiveInputs(classpath); |
| |
| commandLine.addExecPaths("--classpath", classpath); |
| commandLine.add("--nojavac_fallback"); |
| |
| ruleContext.registerAction( |
| builder |
| .addCommandLine( |
| commandLine.build(), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build()) |
| .setMnemonic("Turbine") |
| .build(ruleContext)); |
| return; |
| } |
| |
| // If we get here the action requires annotation processing, so add additional inputs and |
| // flags needed for the javac-based header compiler implementations that supports |
| // annotation processing. |
| |
| builder.addTransitiveInputs(classpathEntries); |
| builder.addTransitiveInputs(plugins.processorClasspath()); |
| builder.addTransitiveInputs(plugins.data()); |
| builder.addTransitiveInputs(compileTimeDependencyArtifacts); |
| |
| commandLine.addExecPaths("--classpath", classpathEntries); |
| commandLine.addAll("--processors", plugins.processorClasses()); |
| commandLine.addExecPaths("--processorpath", plugins.processorClasspath()); |
| if (strictJavaDeps != BuildConfiguration.StrictDepsMode.OFF) { |
| commandLine.addExecPaths("--direct_dependencies", directJars); |
| if (!compileTimeDependencyArtifacts.isEmpty()) { |
| commandLine.addExecPaths("--deps_artifacts", compileTimeDependencyArtifacts); |
| } |
| } |
| if (reduceClasspath && strictJavaDeps != BuildConfiguration.StrictDepsMode.OFF) { |
| commandLine.add("--reduce_classpath"); |
| } else { |
| commandLine.add("--noreduce_classpath"); |
| } |
| |
| ruleContext.registerAction( |
| builder |
| .addCommandLine( |
| commandLine.build(), |
| ParamFileInfo.builder(ParameterFileType.UNQUOTED).setCharset(ISO_8859_1).build()) |
| .setMnemonic("JavacTurbine") |
| .build(ruleContext)); |
| } |
| |
| /** Static class to avoid keeping a reference to this builder after build() is called. */ |
| @AutoCodec.VisibleForSerialization |
| @AutoCodec |
| static class ProgressMessage extends LazyString { |
| |
| private final Artifact outputJar; |
| private final int fileCount; |
| private final NestedSet<String> processorClasses; |
| |
| public ProgressMessage(Artifact outputJar, int fileCount, NestedSet<String> processorClasses) { |
| this.outputJar = outputJar; |
| this.fileCount = fileCount; |
| this.processorClasses = processorClasses; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format( |
| "Compiling Java headers %s (%d files)%s", |
| outputJar.prettyPrint(), |
| fileCount, |
| processorClasses.isEmpty() |
| ? "" |
| : processorClasses.toCollection().stream() |
| .map(name -> name.substring(name.lastIndexOf('.') + 1)) |
| .collect(joining(", ", " and running annotation processors (", ")"))); |
| } |
| } |
| } |