diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java new file mode 100644 index 0000000000..a28d7fd1af --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java @@ -0,0 +1,382 @@ +// Copyright 2014 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.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.base.Preconditions; +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.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.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.syntax.Label; +import com.google.devtools.build.lib.util.FileType; + +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. + * + * <p>Rules that want to use this class are required to have an implicit dependency on the + * Java compiler. + */ +public final class JavaLibraryHelper { + /** + * Function for extracting the {@link JavaCompilationArgs} - note that it also handles .jar files. + */ + private static final Function<TransitiveInfoCollection, JavaCompilationArgsProvider> + TO_COMPILATION_ARGS = new Function<TransitiveInfoCollection, JavaCompilationArgsProvider>() { + @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<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers; + private final JavaCompilationArtifacts compilationArtifacts; + + private Info(Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers, + JavaCompilationArtifacts compilationArtifacts) { + this.providers = Collections.unmodifiableMap(providers); + this.compilationArtifacts = compilationArtifacts; + } + + public Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> getProviders() { + return providers; + } + + public JavaCompilationArtifacts getCompilationArtifacts() { + return compilationArtifacts; + } + } + + private final RuleContext ruleContext; + private final BuildConfiguration configuration; + + private Artifact output; + private final List<Artifact> 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<TransitiveInfoCollection> deps = new ArrayList<>(); + private ImmutableList<String> javacOpts = ImmutableList.of(); + + private StrictDepsMode strictDepsMode = StrictDepsMode.OFF; + private JavaClasspathMode classpathMode = JavaClasspathMode.OFF; + private boolean emitProviders = true; + + public JavaLibraryHelper(RuleContext ruleContext) { + this.ruleContext = ruleContext; + this.configuration = ruleContext.getConfiguration(); + this.classpathMode = ruleContext.getFragment(JavaConfiguration.class).getReduceJavaClasspath(); + } + + /** + * 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<Artifact> 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 extends TransitiveInfoProvider> P getProvider(Class<P> 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<TransitiveInfoProvider> iterator() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Adds the given targets as deps. These are used as both compile-time and runtime dependencies. + */ + public JavaLibraryHelper addDeps(Iterable<? extends TransitiveInfoCollection> deps) { + for (TransitiveInfoCollection dep : deps) { + Preconditions.checkArgument(dep.getConfiguration() == null + || dep.getConfiguration().equals(configuration)); + this.deps.add(dep); + } + return this; + } + + /** + * Sets the compiler options. + */ + public JavaLibraryHelper setJavacOpts(Iterable<String> 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; + } + + /** + * 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) { + addDependencyArtifactsToAttributes(attributes); + } + + JavaCompilationArtifacts.Builder artifactsBuilder = new JavaCompilationArtifacts.Builder(); + JavaCompilationHelper helper = + new JavaCompilationHelper(ruleContext, semantics, javacOpts, attributes); + Artifact outputDepsProto = helper.createOutputDepsProtoArtifact(output, artifactsBuilder); + helper.createCompileAction(output, null, outputDepsProto, null); + helper.createCompileTimeJarAction(output, outputDepsProto, artifactsBuilder); + artifactsBuilder.addRuntimeJar(output); + JavaCompilationArtifacts compilationArtifacts = artifactsBuilder.build(); + + Map<Class<? extends TransitiveInfoProvider>, 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<Artifact> 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<Artifact> getNonRecursiveCompileTimeJarsFromDeps() { + JavaCompilationArgs.Builder builder = JavaCompilationArgs.builder(); + builder.addTransitiveDependencies(transformDeps(), false); + return builder.build().getCompileTimeJars(); + } + + private void addDependencyArtifactsToAttributes(JavaTargetAttributes.Builder attributes) { + NestedSetBuilder<Artifact> compileTimeBuilder = NestedSetBuilder.stableOrder(); + NestedSetBuilder<Artifact> runTimeBuilder = NestedSetBuilder.stableOrder(); + for (JavaCompilationArgsProvider dep : transformDeps()) { + compileTimeBuilder.addTransitive(dep.getCompileTimeJavaDependencyArtifacts()); + runTimeBuilder.addTransitive(dep.getRunTimeJavaDependencyArtifacts()); + } + attributes.addCompileTimeDependencyArtifacts(compileTimeBuilder.build()); + attributes.addRuntimeDependencyArtifacts(runTimeBuilder.build()); + } + + private Iterable<JavaCompilationArgsProvider> 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<Artifact> filesToBuild = + target.getProvider(FileProvider.class).getFilesToBuild(); + final List<Artifact> 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<Artifact> collectTransitiveJavaSourceJars() { + NestedSetBuilder<Artifact> transitiveJavaSourceJarBuilder = + NestedSetBuilder.<Artifact>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() + // 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) { + builder.addTransitiveLangTargets( + deps, + JavaCcLinkParamsProvider.TO_LINK_PARAMS); + builder.addTransitiveTargets(deps); + // TODO(bazel-team): This may need to be optional for some clients of this class. + builder.addTransitiveLangTargets( + deps, + CcSpecificLinkParamsProvider.TO_LINK_PARAMS); + } + }; + } +} |