// 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.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode.OFF; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.UnmodifiableIterator; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode; 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.rules.cpp.CcLinkParams.Builder; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsProvider; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore; import com.google.devtools.build.lib.rules.cpp.CcSpecificLinkParamsProvider; import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaClasspathMode; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * A class to create Java compile actions in a way that is consistent with java_library. Rules that * generate source files and emulate java_library on top of that should use this class * instead of the lower-level API in JavaCompilationHelper. * *

Rules that want to use this class are required to have an implicit dependency on the * Java compiler. */ public final class JavaLibraryHelper { private static final String DEFAULT_SUFFIX_IS_EMPTY_STRING = ""; /** * Function for extracting the {@link JavaCompilationArgs} - note that it also handles .jar files. */ private static final Function TO_COMPILATION_ARGS = new Function() { @Override public JavaCompilationArgsProvider apply(TransitiveInfoCollection target) { return forTarget(target); } }; /** * Contains the providers as well as the compilation outputs. */ public static final class Info { private final Map, TransitiveInfoProvider> providers; private final JavaCompilationArtifacts compilationArtifacts; private Info(Map, TransitiveInfoProvider> providers, JavaCompilationArtifacts compilationArtifacts) { this.providers = Collections.unmodifiableMap(providers); this.compilationArtifacts = compilationArtifacts; } public Map, TransitiveInfoProvider> getProviders() { return providers; } public JavaCompilationArtifacts getCompilationArtifacts() { return compilationArtifacts; } } private final RuleContext ruleContext; private final BuildConfiguration configuration; private final String implicitAttributesSuffix; private Artifact output; private final List sourceJars = new ArrayList<>(); /** * Contains all the dependencies; these are treated as both compile-time and runtime dependencies. * Some of these may not be complete configured targets; for backwards compatibility with some * existing code, we sometimes only have pretend dependencies that only have a single {@link * JavaCompilationArgsProvider}. */ private final List deps = new ArrayList<>(); private ImmutableList javacOpts = ImmutableList.of(); private StrictDepsMode strictDepsMode = StrictDepsMode.OFF; private JavaClasspathMode classpathMode = JavaClasspathMode.OFF; private boolean emitProviders = true; private boolean legacyCollectCppAndJavaLinkOptions; public JavaLibraryHelper(RuleContext ruleContext) { this(ruleContext, DEFAULT_SUFFIX_IS_EMPTY_STRING); } public JavaLibraryHelper(RuleContext ruleContext, String implicitAttributesSuffix) { this.ruleContext = ruleContext; this.configuration = ruleContext.getConfiguration(); this.classpathMode = ruleContext.getFragment(JavaConfiguration.class).getReduceJavaClasspath(); this.implicitAttributesSuffix = implicitAttributesSuffix; } /** * Sets the final output jar; if this is not set, then the {@link #build} method throws an {@link * IllegalStateException}. Note that this class may generate not just the output itself, but also * a number of additional intermediate files and outputs. */ public JavaLibraryHelper setOutput(Artifact output) { this.output = output; return this; } /** * Adds the given source jars. Any .java files in these jars will be compiled. */ public JavaLibraryHelper addSourceJars(Iterable sourceJars) { Iterables.addAll(this.sourceJars, sourceJars); return this; } /** * Adds the given source jars. Any .java files in these jars will be compiled. */ public JavaLibraryHelper addSourceJars(Artifact... sourceJars) { return this.addSourceJars(Arrays.asList(sourceJars)); } /** * Adds the given compilation args as deps. Avoid this method, and prefer {@link #addDeps} * instead; this method only exists for backward compatibility and may be removed at any time. */ public JavaLibraryHelper addProcessedDeps(JavaCompilationArgs... deps) { for (JavaCompilationArgs dep : deps) { this.deps.add(toTransitiveInfoCollection(dep)); } return this; } private static TransitiveInfoCollection toTransitiveInfoCollection( final JavaCompilationArgs args) { return new TransitiveInfoCollection() { @Override public

P getProvider(Class

provider) { if (JavaCompilationArgsProvider.class.equals(provider)) { return provider.cast(new JavaCompilationArgsProvider(args, args)); } return null; } @Override public Label getLabel() { throw new UnsupportedOperationException(); } @Override public BuildConfiguration getConfiguration() { throw new UnsupportedOperationException(); } @Override public Object get(String providerKey) { throw new UnsupportedOperationException(); } @Override public UnmodifiableIterator iterator() { throw new UnsupportedOperationException(); } }; } /** * Adds the given targets as deps. These are used as both compile-time and runtime dependencies. */ public JavaLibraryHelper addDeps(Iterable deps) { for (TransitiveInfoCollection dep : deps) { Preconditions.checkArgument(dep.getConfiguration() == null || configuration.equalsOrIsSupersetOf(dep.getConfiguration())); this.deps.add(dep); } return this; } /** * Sets the compiler options. */ public JavaLibraryHelper setJavacOpts(Iterable javacOpts) { this.javacOpts = ImmutableList.copyOf(javacOpts); return this; } /** * Sets the mode that determines how strictly dependencies are checked. */ public JavaLibraryHelper setStrictDepsMode(StrictDepsMode strictDepsMode) { this.strictDepsMode = strictDepsMode; return this; } /** * Disables all providers, i.e., the resulting {@link Info} object will not contain any providers. * Avoid this method - having this class compute the providers ensures consistency among all * clients of this code. */ public JavaLibraryHelper noProviders() { this.emitProviders = false; return this; } /** * Collects link options from both Java and C++ dependencies. This is never what you want, and * only exists for backwards compatibility. */ public JavaLibraryHelper setLegacyCollectCppAndJavaLinkOptions( boolean legacyCollectCppAndJavaLinkOptions) { this.legacyCollectCppAndJavaLinkOptions = legacyCollectCppAndJavaLinkOptions; return this; } /** * Creates the compile actions and providers. */ public Info build(JavaSemantics semantics) { Preconditions.checkState(output != null, "must have an output file; use setOutput()"); JavaTargetAttributes.Builder attributes = new JavaTargetAttributes.Builder(semantics); attributes.addSourceJars(sourceJars); addDepsToAttributes(attributes); attributes.setStrictJavaDeps(strictDepsMode); attributes.setRuleKind(ruleContext.getRule().getRuleClass()); attributes.setTargetLabel(ruleContext.getLabel()); if (isStrict() && classpathMode != JavaClasspathMode.OFF) { JavaCompilationHelper.addDependencyArtifactsToAttributes(attributes, transformDeps()); } JavaCompilationArtifacts.Builder artifactsBuilder = new JavaCompilationArtifacts.Builder(); JavaCompilationHelper helper = new JavaCompilationHelper( ruleContext, semantics, javacOpts, attributes, implicitAttributesSuffix); Artifact outputDepsProto = helper.createOutputDepsProtoArtifact(output, artifactsBuilder); helper.createCompileAction( output, null /* manifestProtoOutput */, null /* gensrcOutputJar */, outputDepsProto, null /* outputMetadata */); helper.createCompileTimeJarAction(output, artifactsBuilder); artifactsBuilder.addRuntimeJar(output); JavaCompilationArtifacts compilationArtifacts = artifactsBuilder.build(); Map, TransitiveInfoProvider> providers = new LinkedHashMap<>(); if (emitProviders) { providers.put(JavaCompilationArgsProvider.class, collectJavaCompilationArgs(compilationArtifacts)); providers.put(JavaSourceJarsProvider.class, new JavaSourceJarsProvider(collectTransitiveJavaSourceJars(), sourceJars)); providers.put(JavaRunfilesProvider.class, collectJavaRunfiles(compilationArtifacts)); providers.put(JavaCcLinkParamsProvider.class, new JavaCcLinkParamsProvider(createJavaCcLinkParamsStore())); } return new Info(providers, compilationArtifacts); } private void addDepsToAttributes(JavaTargetAttributes.Builder attributes) { NestedSet directJars = null; if (isStrict()) { directJars = getNonRecursiveCompileTimeJarsFromDeps(); if (directJars != null) { attributes.addDirectCompileTimeClassPathEntries(directJars); attributes.addDirectJars(directJars); } } JavaCompilationArgs args = JavaCompilationArgs.builder() .addTransitiveDependencies(transformDeps(), true).build(); attributes.addCompileTimeClassPathEntries(args.getCompileTimeJars()); attributes.addRuntimeClassPathEntries(args.getRuntimeJars()); attributes.addInstrumentationMetadataEntries(args.getInstrumentationMetadata()); } private NestedSet getNonRecursiveCompileTimeJarsFromDeps() { JavaCompilationArgs.Builder builder = JavaCompilationArgs.builder(); builder.addTransitiveDependencies(transformDeps(), false); return builder.build().getCompileTimeJars(); } private Iterable transformDeps() { return Iterables.transform(deps, TO_COMPILATION_ARGS); } private static JavaCompilationArgsProvider forTarget(TransitiveInfoCollection target) { if (target.getProvider(JavaCompilationArgsProvider.class) != null) { // If the target has JavaCompilationArgs, we use those. return target.getProvider(JavaCompilationArgsProvider.class); } else { // Otherwise we look for any jar files. It would be good to remove this, and require // intermediate java_import rules in these cases. NestedSet filesToBuild = target.getProvider(FileProvider.class).getFilesToBuild(); final List jars = new ArrayList<>(); Iterables.addAll(jars, FileType.filter(filesToBuild, JavaSemantics.JAR)); JavaCompilationArgs args = JavaCompilationArgs.builder() .addCompileTimeJars(jars) .addRuntimeJars(jars) .build(); return new JavaCompilationArgsProvider(args, args); } } private boolean isStrict() { return strictDepsMode != OFF; } private JavaCompilationArgsProvider collectJavaCompilationArgs( JavaCompilationArtifacts compilationArtifacts) { JavaCompilationArgs javaCompilationArgs = collectJavaCompilationArgs(compilationArtifacts, false); JavaCompilationArgs recursiveJavaCompilationArgs = collectJavaCompilationArgs(compilationArtifacts, true); return new JavaCompilationArgsProvider(javaCompilationArgs, recursiveJavaCompilationArgs); } /** * Get compilation arguments for java compilation action. * * @param recursive a boolean specifying whether to get transitive * dependencies * @return java compilation args */ private JavaCompilationArgs collectJavaCompilationArgs( JavaCompilationArtifacts compilationArtifacts, boolean recursive) { return JavaCompilationArgs.builder() .merge(compilationArtifacts) .addTransitiveDependencies(transformDeps(), recursive) .build(); } private NestedSet collectTransitiveJavaSourceJars() { NestedSetBuilder transitiveJavaSourceJarBuilder = NestedSetBuilder.stableOrder(); transitiveJavaSourceJarBuilder.addAll(sourceJars); for (JavaSourceJarsProvider other : ruleContext.getPrerequisites( "deps", Mode.TARGET, JavaSourceJarsProvider.class)) { transitiveJavaSourceJarBuilder.addTransitive(other.getTransitiveSourceJars()); } return transitiveJavaSourceJarBuilder.build(); } private JavaRunfilesProvider collectJavaRunfiles( JavaCompilationArtifacts javaCompilationArtifacts) { Runfiles runfiles = new Runfiles.Builder( ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles()) // Compiled templates as well, for API. .addArtifacts(javaCompilationArtifacts.getRuntimeJars()) .addTargets(deps, JavaRunfilesProvider.TO_RUNFILES) .build(); return new JavaRunfilesProvider(runfiles); } private CcLinkParamsStore createJavaCcLinkParamsStore() { return new CcLinkParamsStore() { @Override protected void collect(Builder builder, boolean linkingStatically, boolean linkShared) { if (legacyCollectCppAndJavaLinkOptions) { builder.addTransitiveTargets(deps, JavaCcLinkParamsProvider.TO_LINK_PARAMS); builder.addTransitiveTargets(deps, CcLinkParamsProvider.TO_LINK_PARAMS, CcSpecificLinkParamsProvider.TO_LINK_PARAMS); } else { builder.addTransitiveTargets(deps, JavaCcLinkParamsProvider.TO_LINK_PARAMS, CcLinkParamsProvider.TO_LINK_PARAMS, CcSpecificLinkParamsProvider.TO_LINK_PARAMS); } } }; } }