blob: 6fe894af926818a9f9ad2b4e36eb21b37329e391 [file] [log] [blame]
// Copyright 2014 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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ExecutionRequirements;
import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.LazyWritePathsFileAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.StrictDepsMode;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector;
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.packages.TargetUtils;
import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaClasspathMode;
import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider.JavaPluginInfo;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
/**
* A helper class for compiling Java targets. It contains method to create the various intermediate
* Artifacts for using ijars and source ijars.
*
* <p>Also supports the creation of resource and source only Jars.
*/
public final class JavaCompilationHelper {
private final RuleContext ruleContext;
private final JavaToolchainProvider javaToolchain;
private final JavaRuntimeInfo hostJavabase;
private final JavaTargetAttributes.Builder attributes;
private JavaTargetAttributes builtAttributes;
private final ImmutableList<String> customJavacOpts;
private final ImmutableList<String> customJavacJvmOpts;
private final List<Artifact> translations = new ArrayList<>();
private boolean translationsFrozen;
private final JavaSemantics semantics;
private final ImmutableList<Artifact> additionalJavaBaseInputs;
private final StrictDepsMode strictJavaDeps;
private final String fixDepsTool;
private NestedSet<Artifact> localClassPathEntries = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private static final PathFragment JAVAC = PathFragment.create("_javac");
private JavaCompilationHelper(
RuleContext ruleContext,
JavaSemantics semantics,
ImmutableList<String> javacOpts,
JavaTargetAttributes.Builder attributes,
JavaToolchainProvider javaToolchainProvider,
JavaRuntimeInfo hostJavabase,
ImmutableList<Artifact> additionalJavaBaseInputs,
boolean disableStrictDeps) {
this.ruleContext = ruleContext;
this.javaToolchain = Preconditions.checkNotNull(javaToolchainProvider);
this.hostJavabase = Preconditions.checkNotNull(hostJavabase);
this.attributes = attributes;
this.customJavacOpts = javacOpts;
this.customJavacJvmOpts = javaToolchain.getJavabuilderJvmOptions();
this.semantics = semantics;
this.additionalJavaBaseInputs = additionalJavaBaseInputs;
this.strictJavaDeps =
disableStrictDeps ? StrictDepsMode.OFF : getJavaConfiguration().getFilteredStrictJavaDeps();
this.fixDepsTool = getJavaConfiguration().getFixDepsTool();
}
public JavaCompilationHelper(
RuleContext ruleContext,
JavaSemantics semantics,
ImmutableList<String> javacOpts,
JavaTargetAttributes.Builder attributes,
JavaToolchainProvider javaToolchainProvider,
JavaRuntimeInfo hostJavabase,
ImmutableList<Artifact> additionalJavaBaseInputs) {
this(
ruleContext,
semantics,
javacOpts,
attributes,
javaToolchainProvider,
hostJavabase,
additionalJavaBaseInputs,
false);
}
public JavaCompilationHelper(
RuleContext ruleContext,
JavaSemantics semantics,
ImmutableList<String> javacOpts,
JavaTargetAttributes.Builder attributes) {
this(
ruleContext,
semantics,
javacOpts,
attributes,
JavaToolchainProvider.from(ruleContext),
JavaRuntimeInfo.forHost(ruleContext),
ImmutableList.of());
}
public JavaCompilationHelper(
RuleContext ruleContext,
JavaSemantics semantics,
ImmutableList<String> javacOpts,
JavaTargetAttributes.Builder attributes,
ImmutableList<Artifact> additionalJavaBaseInputs,
boolean disableStrictDeps) {
this(
ruleContext,
semantics,
javacOpts,
attributes,
JavaToolchainProvider.from(ruleContext),
JavaRuntimeInfo.forHost(ruleContext),
additionalJavaBaseInputs,
disableStrictDeps);
}
@VisibleForTesting
JavaCompilationHelper(
RuleContext ruleContext, JavaSemantics semantics, JavaTargetAttributes.Builder attributes) {
this(ruleContext, semantics, getDefaultJavacOptsFromRule(ruleContext), attributes);
}
JavaTargetAttributes getAttributes() {
if (builtAttributes == null) {
builtAttributes = attributes.build();
if (!localClassPathEntries.isEmpty()) {
builtAttributes = builtAttributes.withAdditionalClassPathEntries(localClassPathEntries);
}
}
return builtAttributes;
}
public RuleContext getRuleContext() {
return ruleContext;
}
private AnalysisEnvironment getAnalysisEnvironment() {
return ruleContext.getAnalysisEnvironment();
}
private BuildConfiguration getConfiguration() {
return ruleContext.getConfiguration();
}
private JavaConfiguration getJavaConfiguration() {
return ruleContext.getFragment(JavaConfiguration.class);
}
public JavaCompileOutputs<Artifact> createOutputs(Artifact output) {
JavaCompileOutputs.Builder<Artifact> builder =
JavaCompileOutputs.builder()
.output(output)
.manifestProto(
deriveOutput(
output,
FileSystemUtils.appendExtension(
output.getRootRelativePath(), "_manifest_proto")))
.nativeHeader(deriveOutput(output, "-native-header"));
if (generatesOutputDeps()) {
builder.depsProto(
deriveOutput(
output, FileSystemUtils.replaceExtension(output.getRootRelativePath(), ".jdeps")));
}
if (usesAnnotationProcessing()) {
builder.genClass(deriveOutput(output, "-gen")).genSource(deriveOutput(output, "-gensrc"));
}
JavaCompileOutputs<Artifact> result = builder.build();
return result;
}
public void createCompileAction(JavaCompileOutputs<Artifact> outputs)
throws InterruptedException {
if (outputs.genClass() != null) {
createGenJarAction(
outputs.output(), outputs.manifestProto(), outputs.genClass(), hostJavabase);
}
JavaTargetAttributes attributes = getAttributes();
ImmutableList<Artifact> sourceJars = attributes.getSourceJars();
JavaPluginInfo plugins = attributes.plugins().plugins();
List<Artifact> resourceJars = new ArrayList<>();
boolean turbineAnnotationProcessing =
usesAnnotationProcessing()
&& getJavaConfiguration().experimentalTurbineAnnotationProcessing()
&& Collections.disjoint(
plugins.processorClasses().toSet(),
javaToolchain.getTurbineIncompatibleProcessors());
if (turbineAnnotationProcessing) {
Artifact turbineResources = turbineOutput(outputs.output(), "-turbine-resources.jar");
resourceJars.add(turbineResources);
Artifact turbineJar = turbineOutput(outputs.output(), "-turbine-apt.jar");
Artifact turbineJdeps = turbineOutput(outputs.output(), "-turbine-apt.jdeps");
Artifact turbineGensrc =
outputs.genSource() != null
? outputs.genSource()
: turbineOutput(outputs.output(), "-turbine-apt-gensrc.jar");
JavaHeaderCompileActionBuilder builder = getJavaHeaderCompileActionBuilder();
builder.setOutputJar(turbineJar);
builder.setOutputDepsProto(turbineJdeps);
builder.setPlugins(plugins);
builder.setResourceOutputJar(turbineResources);
builder.setGensrcOutputJar(turbineGensrc);
builder.setManifestOutput(outputs.manifestProto());
builder.setAdditionalOutputs(attributes.getAdditionalOutputs());
// TODO(cushon): GraalVM/native-image doesn't support service-loading for Dagger SPI plugins
builder.enableHeaderCompilerDirect(false);
builder.build(javaToolchain, hostJavabase);
// The sources generated by the turbine annotation processing action are added to the list of
// source jars passed to JavaBuilder.
sourceJars =
ImmutableList.copyOf(Iterables.concat(sourceJars, ImmutableList.of(turbineGensrc)));
}
if (separateResourceJar(resourceJars, attributes)) {
Artifact originalOutput = outputs.output();
outputs =
outputs.withOutput(
ruleContext.getDerivedArtifact(
FileSystemUtils.appendWithoutExtension(
outputs.output().getRootRelativePath(), "-class"),
outputs.output().getRoot()));
resourceJars.add(outputs.output());
createResourceJarAction(originalOutput, ImmutableList.copyOf(resourceJars));
}
JavaCompileActionBuilder builder =
new JavaCompileActionBuilder(
ruleContext.getActionOwner(), ruleContext.getConfiguration(), javaToolchain);
JavaClasspathMode classpathMode = getJavaConfiguration().getReduceJavaClasspath();
if (javaToolchain.getReducedClasspathIncompatibleTargets().contains(ruleContext.getLabel())) {
classpathMode = JavaClasspathMode.OFF;
}
builder.setClasspathMode(classpathMode);
builder.setJavaExecutable(hostJavabase.javaBinaryExecPathFragment());
builder.setJavaBaseInputs(
NestedSetBuilder.fromNestedSet(hostJavabase.javaBaseInputsMiddleman())
.addAll(additionalJavaBaseInputs)
.build());
Label label = ruleContext.getLabel();
builder.setTargetLabel(label);
Artifact coverageArtifact = maybeCreateCoverageArtifact(outputs.output());
builder.setCoverageArtifact(coverageArtifact);
builder.setClasspathEntries(attributes.getCompileTimeClassPath());
builder.setBootclasspathEntries(getBootclasspathOrDefault());
builder.setSourcePathEntries(attributes.getSourcePath());
builder.setToolsJars(javaToolchain.getTools());
builder.setJavaBuilder(javaToolchain.getJavaBuilder());
if (!turbineAnnotationProcessing) {
builder.setGenSourceOutput(outputs.genSource());
builder.setAdditionalOutputs(attributes.getAdditionalOutputs());
builder.setSourceGenDirectory(sourceGenDir(outputs.output(), label));
builder.setPlugins(plugins);
builder.setManifestOutput(outputs.manifestProto());
} else {
// Don't do annotation processing, but pass the processorpath through to allow service-loading
// Error Prone plugins.
builder.setPlugins(
JavaPluginInfo.create(
/* processorClasses= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
plugins.processorClasspath(),
plugins.data()));
}
builder.setOutputs(outputs);
ImmutableSet<Artifact> sourceFiles = attributes.getSourceFiles();
builder.setSourceFiles(sourceFiles);
builder.setSourceJars(sourceJars);
builder.setJavacOpts(customJavacOpts);
builder.setJavacJvmOpts(customJavacJvmOpts);
builder.setJavacExecutionInfo(getExecutionInfo());
builder.setCompressJar(true);
builder.setTempDirectory(tempDir(outputs.output(), label));
builder.setClassDirectory(classDir(outputs.output(), label));
builder.setBuiltinProcessorNames(javaToolchain.getHeaderCompilerBuiltinProcessors());
builder.setExtraData(JavaCommon.computePerPackageData(ruleContext, javaToolchain));
builder.setStrictJavaDeps(attributes.getStrictJavaDeps());
builder.setFixDepsTool(getJavaConfiguration().getFixDepsTool());
builder.setDirectJars(attributes.getDirectJars());
builder.setCompileTimeDependencyArtifacts(attributes.getCompileTimeDependencyArtifacts());
builder.setTargetLabel(
attributes.getTargetLabel() == null ? label : attributes.getTargetLabel());
builder.setInjectingRuleKind(attributes.getInjectingRuleKind());
if (coverageArtifact != null) {
ruleContext.registerAction(
new LazyWritePathsFileAction(
ruleContext.getActionOwner(),
coverageArtifact,
sourceFiles,
/* filesToIgnore= */ ImmutableSet.of(),
false));
}
JavaCompileAction javaCompileAction = builder.build();
ruleContext.getAnalysisEnvironment().registerAction(javaCompileAction);
}
/**
* If there are sources and no resource, the only output is from the javac action. Otherwise
* create a separate jar for the compilation and add resources with singlejar.
*/
private boolean separateResourceJar(
List<Artifact> resourceJars, JavaTargetAttributes attributes) {
return !resourceJars.isEmpty()
|| !attributes.getResources().isEmpty()
|| !attributes.getResourceJars().isEmpty()
|| !attributes.getClassPathResources().isEmpty()
|| !getTranslations().isEmpty();
}
private ImmutableMap<String, String> getExecutionInfo() throws InterruptedException {
ImmutableMap.Builder<String, String> executionInfo = ImmutableMap.builder();
executionInfo.putAll(
getConfiguration()
.modifiedExecutionInfo(
javaToolchain.getJavacSupportsWorkers()
? ExecutionRequirements.WORKER_MODE_ENABLED
: ImmutableMap.of(),
JavaCompileActionBuilder.MNEMONIC));
executionInfo.putAll(
TargetUtils.getExecutionInfo(ruleContext.getRule(), ruleContext.isAllowTagsPropagation()));
return executionInfo.build();
}
/** Returns the bootclasspath explicit set in attributes if present, or else the default. */
public NestedSet<Artifact> getBootclasspathOrDefault() {
JavaTargetAttributes attributes = getAttributes();
if (!attributes.getBootClassPath().isEmpty()) {
return attributes.getBootClassPath();
} else {
return getBootClasspath(javaToolchain);
}
}
public boolean addCoverageSupport() {
FilesToRunProvider jacocoRunner = javaToolchain.getJacocoRunner();
if (jacocoRunner == null) {
return false;
}
Artifact jacocoRunnerJar = jacocoRunner.getExecutable();
if (isStrict()) {
attributes.addDirectJar(jacocoRunnerJar);
}
attributes.addCompileTimeClassPathEntry(jacocoRunnerJar);
attributes.addRuntimeClassPathEntry(jacocoRunnerJar);
return true;
}
/**
* Creates an {@link Artifact} needed by {@code JacocoCoverageRunner}.
*
* <p>The {@link Artifact} is created in the same directory as the given {@code compileJar} and
* has the suffix {@code -paths-for-coverage.txt}.
*
* <p>Returns {@code null} if {@code compileJar} should not be instrumented.
*/
private Artifact maybeCreateCoverageArtifact(Artifact compileJar) {
if (!shouldInstrumentJar()) {
return null;
}
PathFragment packageRelativePath =
compileJar.getRootRelativePath().relativeTo(ruleContext.getPackageDirectory());
PathFragment path =
FileSystemUtils.replaceExtension(packageRelativePath, "-paths-for-coverage.txt");
return ruleContext.getPackageRelativeArtifact(path, compileJar.getRoot());
}
private boolean shouldInstrumentJar() {
RuleContext ruleContext = getRuleContext();
return getConfiguration().isCodeCoverageEnabled()
&& attributes.hasSourceFiles()
&& InstrumentedFilesCollector.shouldIncludeLocalSources(
ruleContext.getConfiguration(), ruleContext.getLabel(), ruleContext.isTestTarget());
}
private boolean shouldUseHeaderCompilation() {
if (!getJavaConfiguration().useHeaderCompilation()) {
return false;
}
if (!attributes.hasSources()) {
return false;
}
if (javaToolchain.getForciblyDisableHeaderCompilation()) {
return false;
}
if (javaToolchain.getHeaderCompiler() == null) {
getRuleContext()
.ruleError(
String.format(
"header compilation was requested but it is not supported by the current Java"
+ " toolchain '%s'; see the java_toolchain.header_compiler attribute",
javaToolchain.getToolchainLabel()));
return false;
}
if (getJavaConfiguration().requireJavaToolchainHeaderCompilerDirect()
&& javaToolchain.getHeaderCompilerDirect() == null) {
getRuleContext()
.ruleError(
String.format(
"header compilation was requested but it is not supported by the current Java"
+ " toolchain '%s'; see the java_toolchain.header_compiler_direct attribute",
javaToolchain.getToolchainLabel()));
return false;
}
return true;
}
private Artifact turbineOutput(Artifact classJar, String newExtension) {
return getAnalysisEnvironment()
.getDerivedArtifact(
FileSystemUtils.replaceExtension(classJar.getRootRelativePath(), newExtension),
classJar.getRoot());
}
/**
* Creates the Action that compiles ijars from source.
*
* @param runtimeJar the jar output of this java compilation, used to create output-relative paths
* for new artifacts.
*/
private Artifact createHeaderCompilationAction(
Artifact runtimeJar, JavaCompilationArtifacts.Builder artifactBuilder)
throws InterruptedException {
Artifact headerJar = turbineOutput(runtimeJar, "-hjar.jar");
Artifact headerDeps = turbineOutput(runtimeJar, "-hjar.jdeps");
JavaTargetAttributes attributes = getAttributes();
// only run API-generating annotation processors during header compilation
JavaPluginInfo plugins = attributes.plugins().apiGeneratingPlugins();
JavaHeaderCompileActionBuilder builder = getJavaHeaderCompileActionBuilder();
builder.setOutputJar(headerJar);
builder.setOutputDepsProto(headerDeps);
builder.setPlugins(plugins);
if (plugins
.processorClasses()
.toList()
.contains("dagger.internal.codegen.ComponentProcessor")) {
// see b/31371210
builder.addJavacOpt("-Aexperimental_turbine_hjar");
}
builder.build(javaToolchain, hostJavabase);
artifactBuilder.setCompileTimeDependencies(headerDeps);
return headerJar;
}
private JavaHeaderCompileActionBuilder getJavaHeaderCompileActionBuilder() {
JavaTargetAttributes attributes = getAttributes();
JavaHeaderCompileActionBuilder builder = new JavaHeaderCompileActionBuilder(getRuleContext());
builder.setSourceFiles(attributes.getSourceFiles());
builder.setSourceJars(attributes.getSourceJars());
builder.setClasspathEntries(attributes.getCompileTimeClassPath());
builder.setBootclasspathEntries(getBootclasspathOrDefault());
// Exclude any per-package configured data (see JavaCommon.computePerPackageData).
// It is used to allow Error Prone checks to load additional data,
// and Error Prone doesn't run during header compilation.
builder.addAllJavacOpts(getJavacOpts());
builder.setStrictJavaDeps(attributes.getStrictJavaDeps());
builder.setCompileTimeDependencyArtifacts(attributes.getCompileTimeDependencyArtifacts());
builder.setDirectJars(attributes.getDirectJars());
builder.setTargetLabel(attributes.getTargetLabel());
builder.setInjectingRuleKind(attributes.getInjectingRuleKind());
builder.setAdditionalInputs(NestedSetBuilder.wrap(Order.LINK_ORDER, additionalJavaBaseInputs));
builder.setToolsJars(javaToolchain.getTools());
return builder;
}
private Artifact deriveOutput(Artifact outputJar, String suffix) {
return deriveOutput(
outputJar, FileSystemUtils.appendWithoutExtension(outputJar.getRootRelativePath(), suffix));
}
private Artifact deriveOutput(Artifact outputJar, PathFragment path) {
return getRuleContext().getDerivedArtifact(path, outputJar.getRoot());
}
/** Returns whether this target uses annotation processing. */
public boolean usesAnnotationProcessing() {
JavaTargetAttributes attributes = getAttributes();
return getJavacOpts().contains("-processor") || attributes.plugins().hasProcessors();
}
public void createGenJarAction(
Artifact classJar,
Artifact manifestProto,
Artifact genClassJar,
JavaRuntimeInfo hostJavabase) {
getRuleContext()
.registerAction(
new SpawnAction.Builder()
.addInput(manifestProto)
.addInput(classJar)
.addOutput(genClassJar)
.addTransitiveInputs(hostJavabase.javaBaseInputsMiddleman())
.setJarExecutable(
JavaCommon.getHostJavaExecutable(hostJavabase),
getGenClassJar(ruleContext),
javaToolchain.getJvmOptions())
.addCommandLine(
CustomCommandLine.builder()
.addExecPath("--manifest_proto", manifestProto)
.addExecPath("--class_jar", classJar)
.addExecPath("--output_jar", genClassJar)
.add("--temp_dir")
.addPath(tempDir(genClassJar, ruleContext.getLabel()))
.build())
.setProgressMessage("Building genclass jar %s", genClassJar.prettyPrint())
.setMnemonic("JavaSourceJar")
.build(getRuleContext()));
}
/** Returns the GenClass deploy jar Artifact. */
private Artifact getGenClassJar(RuleContext ruleContext) {
Artifact genClass = javaToolchain.getGenClass();
if (genClass != null) {
return genClass;
}
return ruleContext.getPrerequisiteArtifact("$genclass", Mode.HOST);
}
/**
* Returns whether this target emits dependency information. Compilation must occur, so certain
* targets acting as aliases have to be filtered out.
*/
private boolean generatesOutputDeps() {
return getJavaConfiguration().getGenerateJavaDeps() && attributes.hasSources();
}
/**
* Creates and registers an Action that packages all of the resources into a Jar. This includes
* the declared resources, the classpath resources and the translated messages.
*/
public void createResourceJarAction(Artifact resourceJar) {
createResourceJarAction(resourceJar, ImmutableList.<Artifact>of());
}
private void createResourceJarAction(Artifact resourceJar, ImmutableList<Artifact> extraJars) {
checkNotNull(resourceJar, "resource jar output must not be null");
JavaTargetAttributes attributes = getAttributes();
new ResourceJarActionBuilder()
.setHostJavaRuntime(hostJavabase)
.setAdditionalInputs(NestedSetBuilder.wrap(Order.STABLE_ORDER, additionalJavaBaseInputs))
.setJavaToolchain(javaToolchain)
.setOutputJar(resourceJar)
.setResources(attributes.getResources())
.setClasspathResources(attributes.getClassPathResources())
.setTranslations(getTranslations())
.setResourceJars(
NestedSetBuilder.fromNestedSet(attributes.getResourceJars()).addAll(extraJars).build())
.build(semantics, ruleContext);
}
/**
* Produces a derived directory where source files generated by annotation processors should be
* stored.
*/
private static PathFragment sourceGenDir(Artifact outputJar, Label label) {
return workDir(outputJar, "_sourcegenfiles", label);
}
private static PathFragment tempDir(Artifact outputJar, Label label) {
return workDir(outputJar, "_temp", label);
}
/** Produces a derived directory where class outputs should be stored. */
public static PathFragment classDir(Artifact outputJar, Label label) {
return workDir(outputJar, "_classes", label);
}
/**
* For an output jar and a suffix, produces a derived directory under the same root as {@code
* label} with directory name composed of {@code outputJar}'s basename and {@code suffix}.
*
* <p>Note that this won't work if a rule produces two jars with the same basename.
*/
private static PathFragment workDir(Artifact outputJar, String suffix, Label label) {
String basename = FileSystemUtils.removeExtension(outputJar.getExecPath().getBaseName());
return outputJar
.getRoot()
.getExecPath()
.getRelative(AnalysisUtils.getUniqueDirectory(label, JAVAC))
.getRelative(basename + suffix);
}
/**
* Creates an Action that packages the Java source files into a Jar. If {@code gensrcJar} is
* non-null, includes the contents of the {@code gensrcJar} with the output source jar.
*
* @param outputJar the Artifact to create with the Action
* @param gensrcJar the generated sources jar Artifact that should be included with the sources in
* the output Artifact. May be null.
* @param javaToolchainProvider is used by SingleJarActionBuilder to retrieve jvm options
* @param hostJavabase the Java runtime used to run the binaries in the action
*/
public void createSourceJarAction(
Artifact outputJar,
@Nullable Artifact gensrcJar,
JavaToolchainProvider javaToolchainProvider,
JavaRuntimeInfo hostJavabase) {
JavaTargetAttributes attributes = getAttributes();
NestedSetBuilder<Artifact> resourceJars = NestedSetBuilder.stableOrder();
resourceJars.addAll(attributes.getSourceJars());
if (gensrcJar != null) {
resourceJars.add(gensrcJar);
}
SingleJarActionBuilder.createSourceJarAction(
ruleContext,
ruleContext,
semantics,
NestedSetBuilder.<Artifact>wrap(Order.STABLE_ORDER, attributes.getSourceFiles()),
resourceJars.build(),
outputJar,
javaToolchainProvider,
hostJavabase);
}
public void createSourceJarAction(Artifact outputJar, @Nullable Artifact gensrcJar) {
JavaTargetAttributes attributes = getAttributes();
NestedSetBuilder<Artifact> resourceJars = NestedSetBuilder.stableOrder();
resourceJars.addAll(attributes.getSourceJars());
if (gensrcJar != null) {
resourceJars.add(gensrcJar);
}
SingleJarActionBuilder.createSourceJarAction(
ruleContext,
semantics,
NestedSetBuilder.<Artifact>wrap(Order.STABLE_ORDER, attributes.getSourceFiles()),
resourceJars.build(),
outputJar);
}
/**
* Creates the actions that produce the interface jar. Adds the jar artifacts to the given
* JavaCompilationArtifacts builder.
*
* @return the header jar (if requested), or ijar (if requested), or else the class jar
*/
public Artifact createCompileTimeJarAction(
Artifact runtimeJar, JavaCompilationArtifacts.Builder builder) throws InterruptedException {
Artifact jar;
boolean isFullJar = false;
if (shouldUseHeaderCompilation()) {
jar = createHeaderCompilationAction(runtimeJar, builder);
} else if (getJavaConfiguration().getUseIjars()) {
JavaTargetAttributes attributes = getAttributes();
jar =
createIjarAction(
ruleContext,
javaToolchain,
runtimeJar,
attributes.getTargetLabel(),
attributes.getInjectingRuleKind(),
false);
} else {
jar = runtimeJar;
isFullJar = true;
}
if (isFullJar) {
builder.addCompileTimeJarAsFullJar(jar);
} else {
builder.addInterfaceJarWithFullJar(jar, runtimeJar);
}
return jar;
}
private void addArgsAndJarsToAttributes(
JavaCompilationArgsProvider args, NestedSet<Artifact> directJars) {
// Can only be non-null when isStrict() returns true.
if (directJars != null) {
attributes.addDirectJars(directJars);
}
attributes.merge(args);
}
/**
* Adds compile-time dependencies that will be included on the classpath, but which will not be
* visible to targets that depend on the current compilation.
*/
public void addLocalClassPathEntries(NestedSet<Artifact> localClassPathEntries) {
checkState(
builtAttributes == null,
"addLocalClassPathEntries must be called before the first call to getAttributes()");
this.localClassPathEntries = localClassPathEntries;
}
private void addLibrariesToAttributesInternal(Iterable<? extends TransitiveInfoCollection> deps) {
JavaCompilationArgsProvider args = JavaCompilationArgsProvider.legacyFromTargets(deps);
NestedSet<Artifact> directJars =
isStrict() ? getNonRecursiveCompileTimeJarsFromCollection(deps) : null;
addArgsAndJarsToAttributes(args, directJars);
}
private boolean isStrict() {
return getStrictJavaDeps() != StrictDepsMode.OFF;
}
private NestedSet<Artifact> getNonRecursiveCompileTimeJarsFromCollection(
Iterable<? extends TransitiveInfoCollection> deps) {
return JavaCompilationArgsProvider.legacyFromTargets(deps).getDirectCompileTimeJars();
}
static void addDependencyArtifactsToAttributes(
JavaTargetAttributes.Builder attributes,
Iterable<? extends JavaCompilationArgsProvider> deps) {
NestedSetBuilder<Artifact> result = NestedSetBuilder.stableOrder();
for (JavaCompilationArgsProvider provider : deps) {
result.addTransitive(provider.getCompileTimeJavaDependencyArtifacts());
}
attributes.addCompileTimeDependencyArtifacts(result.build());
}
/**
* Adds the compile time and runtime Java libraries in the transitive closure of the deps to the
* attributes.
*
* @param deps the dependencies to be included as roots of the transitive closure
*/
public void addLibrariesToAttributes(Iterable<? extends TransitiveInfoCollection> deps) {
// Enforcing strict Java dependencies: when the --strict_java_deps flag is
// WARN or ERROR, or is DEFAULT and strict_java_deps attribute is unset,
// we use a stricter javac compiler to perform direct deps checks.
attributes.setStrictJavaDeps(getStrictJavaDeps());
addLibrariesToAttributesInternal(deps);
JavaClasspathMode classpathMode = getJavaConfiguration().getReduceJavaClasspath();
if (isStrict() && classpathMode != JavaClasspathMode.OFF) {
List<JavaCompilationArgsProvider> compilationArgsProviders = new ArrayList<>();
for (TransitiveInfoCollection dep : deps) {
JavaCompilationArgsProvider provider =
JavaInfo.getProvider(JavaCompilationArgsProvider.class, dep);
if (provider != null) {
compilationArgsProviders.add(provider);
}
}
addDependencyArtifactsToAttributes(attributes, compilationArgsProviders);
}
}
/**
* Determines whether to enable strict_java_deps.
*
* @return filtered command line flag value, defaulting to ERROR
*/
public StrictDepsMode getStrictJavaDeps() {
return strictJavaDeps;
}
/** Determines which tool to use when fixing dependency errors. */
public String getFixDepsTool() {
return fixDepsTool;
}
/**
* Gets the value of the "javacopts" attribute combining them with the default options. If the
* current rule has no javacopts attribute, this method only returns the default options.
*/
@VisibleForTesting
public ImmutableList<String> getJavacOpts() {
return customJavacOpts;
}
/**
* Obtains the standard list of javac opts needed to build {@code rule}.
*
* <p>This method must only be called during initialization.
*
* @param ruleContext a rule context
* @return a list of options to provide to javac
*/
private static ImmutableList<String> getDefaultJavacOptsFromRule(RuleContext ruleContext) {
return ImmutableList.copyOf(
Iterables.concat(
JavaToolchainProvider.from(ruleContext).getJavacOptions(ruleContext),
ruleContext.getExpander().withDataLocations().tokenized("javacopts")));
}
public void setTranslations(Collection<Artifact> translations) {
Preconditions.checkArgument(!translationsFrozen);
this.translations.addAll(translations);
}
private ImmutableList<Artifact> getTranslations() {
translationsFrozen = true;
return ImmutableList.copyOf(translations);
}
/**
* Returns the javac bootclasspath artifacts from the given toolchain (if it has any) or the rule.
*/
public static NestedSet<Artifact> getBootClasspath(JavaToolchainProvider javaToolchain) {
return javaToolchain.getBootclasspath();
}
/**
* Creates the Action that creates ijars from Jar files.
*
* @param inputJar the Jar to create the ijar for
* @param addPrefix whether to prefix the path of the generated ijar with the package and name of
* the current rule
* @return the Artifact to create with the Action
*/
protected static Artifact createIjarAction(
RuleContext ruleContext,
JavaToolchainProvider javaToolchain,
Artifact inputJar,
@Nullable Label targetLabel,
@Nullable String injectingRuleKind,
boolean addPrefix) {
Artifact interfaceJar = getIjarArtifact(ruleContext, inputJar, addPrefix);
FilesToRunProvider ijarTarget = javaToolchain.getIjar();
if (!ruleContext.hasErrors()) {
CustomCommandLine.Builder commandLine =
CustomCommandLine.builder().addExecPath(inputJar).addExecPath(interfaceJar);
if (targetLabel != null) {
commandLine.addLabel("--target_label", targetLabel);
}
if (injectingRuleKind != null) {
commandLine.add("--injecting_rule_kind", injectingRuleKind);
}
ruleContext.registerAction(
new SpawnAction.Builder()
.addInput(inputJar)
.addOutput(interfaceJar)
.setExecutable(ijarTarget)
// On Windows, ijar.exe needs msys-2.0.dll and zlib1.dll in PATH.
// Use default shell environment so that those can be found.
// TODO(dslomov): revisit this. If ijar is not msys-dependent, this is not needed.
.useDefaultShellEnvironment()
.setProgressMessage("Extracting interface %s", ruleContext.getLabel())
.setMnemonic("JavaIjar")
.addCommandLine(commandLine.build())
.build(ruleContext));
}
return interfaceJar;
}
private static Artifact getIjarArtifact(
RuleContext ruleContext, Artifact jar, boolean addPrefix) {
if (addPrefix) {
PathFragment ruleBase = ruleContext.getUniqueDirectory("_ijar");
PathFragment artifactDirFragment = jar.getRootRelativePath().getParentDirectory();
String ijarBasename = FileSystemUtils.removeExtension(jar.getFilename()) + "-ijar.jar";
return ruleContext.getDerivedArtifact(
ruleBase.getRelative(artifactDirFragment).getRelative(ijarBasename),
ruleContext
.getConfiguration()
.getGenfilesDirectory(ruleContext.getRule().getRepository()));
} else {
return derivedArtifact(ruleContext, jar, "", "-ijar.jar");
}
}
/**
* Creates a derived artifact from the given artifact by adding the given prefix and removing the
* extension and replacing it by the given suffix. The new artifact will have the same root as the
* given one.
*/
static Artifact derivedArtifact(
ActionConstructionContext context, Artifact artifact, String prefix, String suffix) {
PathFragment path = artifact.getRootRelativePath();
String basename = FileSystemUtils.removeExtension(path.getBaseName()) + suffix;
path = path.replaceName(prefix + basename);
return context.getDerivedArtifact(path, artifact.getRoot());
}
}