// 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 com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.AnalysisEnvironment; import com.google.devtools.build.lib.analysis.AnalysisUtils; import com.google.devtools.build.lib.analysis.OutputGroupProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; import com.google.devtools.build.lib.analysis.Util; 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.BuildType; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.lib.rules.cpp.CppCompilationContext; import com.google.devtools.build.lib.rules.cpp.LinkerInput; import com.google.devtools.build.lib.rules.java.JavaCompilationArgs.ClasspathType; import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector; import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.InstrumentationSpec; import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.LocalMetadataCollector; import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.util.FileTypeSet; import com.google.devtools.build.lib.util.Preconditions; 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.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.annotation.Nullable; /** * A helper class to create configured targets for Java rules. */ public class JavaCommon { private static final Function GET_COLLECTION_LABEL = new Function() { @Override public Label apply(TransitiveInfoCollection collection) { return collection.getLabel(); } }; public static final InstrumentationSpec JAVA_COLLECTION_SPEC = new InstrumentationSpec( FileTypeSet.of(JavaSemantics.JAVA_SOURCE)) .withSourceAttributes("srcs") .withDependencyAttributes("deps", "data", "exports", "runtime_deps"); /** * Collects all metadata files generated by Java compilation actions. */ private static final LocalMetadataCollector JAVA_METADATA_COLLECTOR = new LocalMetadataCollector() { @Override public void collectMetadataArtifacts(Iterable objectFiles, AnalysisEnvironment analysisEnvironment, NestedSetBuilder metadataFilesBuilder) { for (Artifact artifact : objectFiles) { Action action = analysisEnvironment.getLocalGeneratingAction(artifact); if (action instanceof JavaCompileAction) { addOutputs(metadataFilesBuilder, action, JavaSemantics.COVERAGE_METADATA); } } } }; private ClasspathConfiguredFragment classpathFragment = new ClasspathConfiguredFragment(); private JavaCompilationArtifacts javaArtifacts = JavaCompilationArtifacts.EMPTY; private ImmutableList javacOpts; // Targets treated as deps in compilation time, runtime time and both private final ImmutableMap> targetsTreatedAsDeps; private final ImmutableList sources; private ImmutableList activePlugins = ImmutableList.of(); private final RuleContext ruleContext; private final JavaSemantics semantics; private JavaCompilationHelper javaCompilationHelper; public JavaCommon(RuleContext ruleContext, JavaSemantics semantics) { this(ruleContext, semantics, ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list(), collectTargetsTreatedAsDeps(ruleContext, semantics, ClasspathType.COMPILE_ONLY), collectTargetsTreatedAsDeps(ruleContext, semantics, ClasspathType.RUNTIME_ONLY), collectTargetsTreatedAsDeps(ruleContext, semantics, ClasspathType.BOTH)); } public JavaCommon(RuleContext ruleContext, JavaSemantics semantics, ImmutableList sources) { this(ruleContext, semantics, sources, collectTargetsTreatedAsDeps(ruleContext, semantics, ClasspathType.COMPILE_ONLY), collectTargetsTreatedAsDeps(ruleContext, semantics, ClasspathType.RUNTIME_ONLY), collectTargetsTreatedAsDeps(ruleContext, semantics, ClasspathType.BOTH)); } public JavaCommon(RuleContext ruleContext, JavaSemantics semantics, ImmutableList compileDeps, ImmutableList runtimeDeps, ImmutableList bothDeps) { this(ruleContext, semantics, ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list(), compileDeps, runtimeDeps, bothDeps); } public JavaCommon(RuleContext ruleContext, JavaSemantics semantics, ImmutableList sources, ImmutableList compileDeps, ImmutableList runtimeDeps, ImmutableList bothDeps) { this.ruleContext = ruleContext; this.semantics = semantics; this.sources = sources; this.targetsTreatedAsDeps = ImmutableMap.of( ClasspathType.COMPILE_ONLY, compileDeps, ClasspathType.RUNTIME_ONLY, runtimeDeps, ClasspathType.BOTH, bothDeps); } public JavaSemantics getJavaSemantics() { return semantics; } /** * Validates that the packages listed under "deps" all have the given constraint. If a package * does not have this attribute, an error is generated. */ public static final void validateConstraint(RuleContext ruleContext, String constraint, Iterable targets) { for (JavaConstraintProvider constraintProvider : AnalysisUtils.getProviders(targets, JavaConstraintProvider.class)) { if (!constraintProvider.getJavaConstraints().contains(constraint)) { ruleContext.attributeError("deps", String.format("%s: does not have constraint '%s'", constraintProvider.getLabel(), constraint)); } } } /** * Creates an action to aggregate all metadata artifacts into a single * <target_name>_instrumented.jar file. */ public static void createInstrumentedJarAction(RuleContext ruleContext, JavaSemantics semantics, List metadataArtifacts, Artifact instrumentedJar, String mainClass) { // In Jacoco's setup, metadata artifacts are real jars. new DeployArchiveBuilder(semantics, ruleContext) .setOutputJar(instrumentedJar) // We need to save the original mainClass because we're going to run inside CoverageRunner .setJavaStartClass(mainClass) .setAttributes(new JavaTargetAttributes.Builder(semantics).build()) .addRuntimeJars(ImmutableList.copyOf(metadataArtifacts)) .setCompression(DeployArchiveBuilder.Compression.UNCOMPRESSED) .build(); } public static ImmutableList getConstraints(RuleContext ruleContext) { return ruleContext.getRule().isAttrDefined("constraints", Type.STRING_LIST) ? ImmutableList.copyOf(ruleContext.attributes().get("constraints", Type.STRING_LIST)) : ImmutableList.of(); } public void setClassPathFragment(ClasspathConfiguredFragment classpathFragment) { this.classpathFragment = classpathFragment; } public void setJavaCompilationArtifacts(JavaCompilationArtifacts javaArtifacts) { this.javaArtifacts = javaArtifacts; } public JavaCompilationArtifacts getJavaCompilationArtifacts() { return javaArtifacts; } public ImmutableList getProcessorClasspathJars() { Set processorClasspath = new LinkedHashSet<>(); for (JavaPluginInfoProvider plugin : activePlugins) { for (Artifact classpathJar : plugin.getProcessorClasspath()) { processorClasspath.add(classpathJar); } } return ImmutableList.copyOf(processorClasspath); } public ImmutableList getProcessorClassNames() { Set processorNames = new LinkedHashSet<>(); for (JavaPluginInfoProvider plugin : activePlugins) { processorNames.addAll(plugin.getProcessorClasses()); } return ImmutableList.copyOf(processorNames); } /** * Creates the java.library.path from a list of the native libraries. * Concatenates the parent directories of the shared libraries into a Java * search path. Each relative path entry is prepended with "${JAVA_RUNFILES}/" * so it can be resolved at runtime. * * @param sharedLibraries a collection of native libraries to create the java * library path from * @return a String containing the ":" separated java library path */ public static String javaLibraryPath( Collection sharedLibraries, String runfilePrefix) { StringBuilder buffer = new StringBuilder(); Set entries = new HashSet<>(); for (Artifact sharedLibrary : sharedLibraries) { PathFragment entry = sharedLibrary.getRootRelativePath().getParentDirectory(); if (entries.add(entry)) { if (buffer.length() > 0) { buffer.append(':'); } buffer.append("${JAVA_RUNFILES}/" + runfilePrefix + "/"); buffer.append(entry.getPathString()); } } return buffer.toString(); } /** * Collects Java compilation arguments for this target. * * @param recursive Whether to scan dependencies recursively. * @param isNeverLink Whether the target has the 'neverlink' attr. * @param srcLessDepsExport If srcs is omitted, deps are exported * (deprecated behaviour for android_library only) */ public JavaCompilationArgs collectJavaCompilationArgs(boolean recursive, boolean isNeverLink, Iterable compilationArgsFromSources, boolean srcLessDepsExport) { ClasspathType type = isNeverLink ? ClasspathType.COMPILE_ONLY : ClasspathType.BOTH; JavaCompilationArgs.Builder builder = JavaCompilationArgs.builder() .merge(getJavaCompilationArtifacts(), isNeverLink) .addTransitiveTargets(getExports(ruleContext), recursive, type); // TODO(bazel-team): remove srcs-less behaviour after android_library users are refactored if (recursive || srcLessDepsExport) { builder .addTransitiveTargets(targetsTreatedAsDeps(ClasspathType.COMPILE_ONLY), recursive, type) .addTransitiveTargets(getRuntimeDeps(ruleContext), recursive, ClasspathType.RUNTIME_ONLY) .addSourcesTransitiveCompilationArgs(compilationArgsFromSources, recursive, type); } return builder.build(); } /** * Collects Java dependency artifacts for this target. * * @param outDeps output (compile-time) dependency artifact of this target */ public NestedSet collectCompileTimeDependencyArtifacts(@Nullable Artifact outDeps) { NestedSetBuilder builder = NestedSetBuilder.stableOrder(); if (outDeps != null) { builder.add(outDeps); } for (JavaCompilationArgsProvider provider : AnalysisUtils.getProviders( getExports(ruleContext), JavaCompilationArgsProvider.class)) { builder.addTransitive(provider.getCompileTimeJavaDependencyArtifacts()); } return builder.build(); } public static List getExports(RuleContext ruleContext) { // We need to check here because there are classes inheriting from this class that implement // rules that don't have this attribute. if (ruleContext.attributes().has("exports", BuildType.LABEL_LIST)) { // Do not remove , BuildConfiguration>: // workaround for Java 7 type inference. return ImmutableList.copyOf( ruleContext.getPrerequisites("exports", Mode.TARGET)); } else { return ImmutableList.of(); } } /** * Sanity checks the given runtime dependencies, and emits errors if there is a problem. * Also called by {@link #initCommon()} for the current target's runtime dependencies. */ public static void checkRuntimeDeps( RuleContext ruleContext, List runtimeDepInfo) { for (TransitiveInfoCollection c : runtimeDepInfo) { JavaNeverlinkInfoProvider neverLinkedness = c.getProvider(JavaNeverlinkInfoProvider.class); if (neverLinkedness == null) { continue; } boolean reportError = !ruleContext.getConfiguration().getAllowRuntimeDepsOnNeverLink(); if (neverLinkedness.isNeverlink()) { String msg = String.format("neverlink dep %s not allowed in runtime deps", c.getLabel()); if (reportError) { ruleContext.attributeError("runtime_deps", msg); } else { ruleContext.attributeWarning("runtime_deps", msg); } } } } /** * Returns transitive Java native libraries. * * @see JavaNativeLibraryProvider */ protected NestedSet collectTransitiveJavaNativeLibraries() { NativeLibraryNestedSetBuilder builder = new NativeLibraryNestedSetBuilder(); builder.addJavaTargets(targetsTreatedAsDeps(ClasspathType.BOTH)); if (ruleContext.getRule().isAttrDefined("data", BuildType.LABEL_LIST)) { builder.addJavaTargets(ruleContext.getPrerequisites("data", Mode.DATA)); } return builder.build(); } /** * Collects transitive source jars for the current rule. * * @param targetSrcJar The source jar artifact corresponding to the output of the current rule. * @return A nested set containing all of the source jar artifacts on which the current rule * transitively depends. */ public NestedSet collectTransitiveSourceJars(Artifact targetSrcJar) { NestedSetBuilder builder = NestedSetBuilder.stableOrder(); builder.add(targetSrcJar); for (JavaSourceJarsProvider dep : getDependencies(JavaSourceJarsProvider.class)) { builder.addTransitive(dep.getTransitiveSourceJars()); } return builder.build(); } /** * Collects transitive gen jars for the current rule. */ private JavaGenJarsProvider collectTransitiveGenJars( boolean usesAnnotationProcessing, @Nullable Artifact genClassJar, @Nullable Artifact genSourceJar) { NestedSetBuilder classJarsBuilder = NestedSetBuilder.stableOrder(); NestedSetBuilder sourceJarsBuilder = NestedSetBuilder.stableOrder(); if (genClassJar != null) { classJarsBuilder.add(genClassJar); } if (genSourceJar != null) { sourceJarsBuilder.add(genSourceJar); } for (JavaGenJarsProvider dep : getDependencies(JavaGenJarsProvider.class)) { classJarsBuilder.addTransitive(dep.getTransitiveGenClassJars()); sourceJarsBuilder.addTransitive(dep.getTransitiveGenSourceJars()); } return new JavaGenJarsProvider( usesAnnotationProcessing, genClassJar, genSourceJar, classJarsBuilder.build(), sourceJarsBuilder.build() ); } /** * Collects transitive C++ dependencies. */ protected CppCompilationContext collectTransitiveCppDeps() { CppCompilationContext.Builder builder = new CppCompilationContext.Builder(ruleContext); for (TransitiveInfoCollection dep : targetsTreatedAsDeps(ClasspathType.BOTH)) { CppCompilationContext context = dep.getProvider(CppCompilationContext.class); if (context != null) { builder.mergeDependentContext(context); } } return builder.build(); } /** * Collects labels of targets and artifacts reached transitively via the "exports" attribute. */ protected JavaExportsProvider collectTransitiveExports() { NestedSetBuilder