// 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.Predicates; 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.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.rules.cpp.CppFileTypes; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * An object that captures the temporary state we need to pass around while * the initialization hook for a java rule is running. */ public class JavaTargetAttributes { private static void checkJar(Artifact classPathEntry) { if (!JavaSemantics.JAR.matches(classPathEntry.getFilename())) { throw new IllegalArgumentException( "not a jar file: " + classPathEntry.prettyPrint()); } } /** * A builder class for JavaTargetAttributes. */ public static class Builder { // The order of source files is important, and there must not be duplicates. // Unfortunately, there is no interface in Java that represents a collection // without duplicates that has a stable and deterministic iteration order, // but is not sorted according to a property of the elements. Thus we are // stuck with Set. private final Set<Artifact> sourceFiles = new LinkedHashSet<>(); private final NestedSetBuilder<Artifact> runtimeClassPath = NestedSetBuilder.naiveLinkOrder(); private final NestedSetBuilder<Artifact> compileTimeClassPath = NestedSetBuilder.naiveLinkOrder(); private final List<Artifact> bootClassPath = new ArrayList<>(); private final List<Artifact> sourcePath = new ArrayList<>(); private final List<Artifact> nativeLibraries = new ArrayList<>(); private final NestedSetBuilder<Artifact> processorPath = NestedSetBuilder.naiveLinkOrder(); // Classpath directories can't be represented as artifacts (TreeArtifact isn't appropriate // here since all we need is a path string to apply to the command line). private final Set<String> processorNames = new LinkedHashSet<>(); private final Set<String> processorFlags = new LinkedHashSet<>(); private final NestedSetBuilder<Artifact> apiGeneratingProcessorPath = NestedSetBuilder.naiveLinkOrder(); private final Set<String> apiGeneratingProcessorNames = new LinkedHashSet<>(); private final Map<PathFragment, Artifact> resources = new LinkedHashMap<>(); private final NestedSetBuilder<Artifact> resourceJars = NestedSetBuilder.stableOrder(); private final List<Artifact> messages = new ArrayList<>(); private final List<Artifact> instrumentationMetadata = new ArrayList<>(); private final List<Artifact> sourceJars = new ArrayList<>(); private final List<Artifact> classPathResources = new ArrayList<>(); private final Set<Artifact> additionalOutputs = new LinkedHashSet<>(); private BuildConfiguration.StrictDepsMode strictJavaDeps = BuildConfiguration.StrictDepsMode.OFF; private final NestedSetBuilder<Artifact> directJars = NestedSetBuilder.naiveLinkOrder(); private final NestedSetBuilder<Artifact> compileTimeDependencyArtifacts = NestedSetBuilder.stableOrder(); private String ruleKind; private Label targetLabel; private final NestedSetBuilder<Artifact> excludedArtifacts = NestedSetBuilder.naiveLinkOrder(); private boolean built = false; private final JavaSemantics semantics; public Builder(JavaSemantics semantics) { this.semantics = semantics; } public Builder addSourceArtifacts(Iterable<Artifact> sourceArtifacts) { Preconditions.checkArgument(!built); for (Artifact srcArtifact : sourceArtifacts) { String srcFilename = srcArtifact.getExecPathString(); if (JavaSemantics.SOURCE_JAR.matches(srcFilename)) { sourceJars.add(srcArtifact); } else if (JavaSemantics.PROPERTIES.matches(srcFilename)) { // output files of the message compiler resources.put( semantics.getDefaultJavaResourcePath(srcArtifact.getRootRelativePath()), srcArtifact); } else if (JavaSemantics.JAVA_SOURCE.matches(srcFilename)) { sourceFiles.add(srcArtifact); } else { // try specific cases from the semantics. semantics.addArtifactToJavaTargetAttribute(this, srcArtifact); } } return this; } public Builder addSourceFiles(Iterable<Artifact> sourceFiles) { Preconditions.checkArgument(!built); for (Artifact artifact : sourceFiles) { if (JavaSemantics.JAVA_SOURCE.matches(artifact.getFilename())) { this.sourceFiles.add(artifact); } } return this; } public Builder merge(JavaCompilationArgs context) { Preconditions.checkArgument(!built); addCompileTimeClassPathEntries(context.getCompileTimeJars()); addRuntimeClassPathEntries(context.getRuntimeJars()); addInstrumentationMetadataEntries(context.getInstrumentationMetadata()); return this; } public Builder addSourceJars(Collection<Artifact> sourceJars) { Preconditions.checkArgument(!built); this.sourceJars.addAll(sourceJars); return this; } public Builder addSourceJar(Artifact sourceJar) { Preconditions.checkArgument(!built); this.sourceJars.add(sourceJar); return this; } public Builder addRuntimeClassPathEntry(Artifact classPathEntry) { Preconditions.checkArgument(!built); checkJar(classPathEntry); runtimeClassPath.add(classPathEntry); return this; } public Builder addRuntimeClassPathEntries(NestedSet<Artifact> classPathEntries) { Preconditions.checkArgument(!built); runtimeClassPath.addTransitive(classPathEntries); return this; } public Builder addCompileTimeClassPathEntries(NestedSet<Artifact> entries) { Preconditions.checkArgument(!built); compileTimeClassPath.addTransitive(entries); return this; } public Builder setRuleKind(String ruleKind) { Preconditions.checkArgument(!built); this.ruleKind = ruleKind; return this; } public Builder setTargetLabel(Label targetLabel) { Preconditions.checkArgument(!built); this.targetLabel = targetLabel; return this; } /** * Sets the bootclasspath to be passed to the Java compiler. * * <p>If this method is called, then the bootclasspath specified in this JavaTargetAttributes * instance overrides the default bootclasspath. */ public Builder setBootClassPath(List<Artifact> jars) { Preconditions.checkArgument(!built); Preconditions.checkArgument(!jars.isEmpty()); Preconditions.checkState(bootClassPath.isEmpty()); bootClassPath.addAll(jars); return this; } /** * Sets the sourcepath to be passed to the Java compiler. */ public Builder setSourcePath(List<Artifact> artifacts) { Preconditions.checkArgument(!built); Preconditions.checkArgument(sourcePath.isEmpty()); sourcePath.addAll(artifacts); return this; } public Builder addExcludedArtifacts(NestedSet<Artifact> toExclude) { Preconditions.checkArgument(!built); excludedArtifacts.addTransitive(toExclude); return this; } /** * Controls how strict the javac compiler will be in checking correct use of * direct dependencies. * * @param strictDeps one of WARN, ERROR or OFF */ public Builder setStrictJavaDeps(BuildConfiguration.StrictDepsMode strictDeps) { Preconditions.checkArgument(!built); strictJavaDeps = strictDeps; return this; } /** * In tandem with strictJavaDeps, directJars represents a subset of the compile-time, classpath * jars that were provided by direct dependencies. When strictJavaDeps is OFF, there is no need * to provide directJars, and no extra information is passed to javac. When strictJavaDeps is * set to WARN or ERROR, the compiler command line will include extra flags to indicate the * warning/error policy and to map the classpath jars to direct or transitive dependencies, * using the information in directJars. The extra flags are formatted like this (same for * --indirect_dependency): <pre> * --direct_dependency * foo/bar/lib.jar * //java/com/google/foo:bar * </pre> */ public Builder addDirectJars(NestedSet<Artifact> directJars) { Preconditions.checkArgument(!built); this.directJars.addTransitive(directJars); return this; } public Builder addCompileTimeDependencyArtifacts(NestedSet<Artifact> dependencyArtifacts) { Preconditions.checkArgument(!built); compileTimeDependencyArtifacts.addTransitive(dependencyArtifacts); return this; } public Builder addInstrumentationMetadataEntries(Iterable<Artifact> metadataEntries) { Preconditions.checkArgument(!built); Iterables.addAll(instrumentationMetadata, metadataEntries); return this; } public Builder addNativeLibrary(Artifact nativeLibrary) { Preconditions.checkArgument(!built); String name = nativeLibrary.getFilename(); if (CppFileTypes.INTERFACE_SHARED_LIBRARY.matches(name)) { return this; } if (!(CppFileTypes.SHARED_LIBRARY.matches(name) || CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name))) { throw new IllegalArgumentException("not a shared library :" + nativeLibrary.prettyPrint()); } nativeLibraries.add(nativeLibrary); return this; } public Builder addNativeLibraries(Iterable<Artifact> nativeLibraries) { Preconditions.checkArgument(!built); for (Artifact nativeLibrary : nativeLibraries) { addNativeLibrary(nativeLibrary); } return this; } public Builder addMessages(Collection<Artifact> messages) { Preconditions.checkArgument(!built); this.messages.addAll(messages); return this; } public Builder addMessage(Artifact messagesArtifact) { Preconditions.checkArgument(!built); this.messages.add(messagesArtifact); return this; } public Builder addResource(PathFragment execPath, Artifact resource) { Preconditions.checkArgument(!built); this.resources.put(execPath, resource); return this; } public Builder addResourceJars(NestedSet<Artifact> resourceJars) { Preconditions.checkArgument(!built); this.resourceJars.addTransitive(resourceJars); return this; } public Builder addProcessorName(String processor) { Preconditions.checkArgument(!built); processorNames.add(processor); return this; } public Builder addProcessorFlag(String processorFlag) { Preconditions.checkArgument(!built); processorFlags.add(processorFlag); return this; } public Builder addProcessorPath(NestedSet<Artifact> jars) { Preconditions.checkArgument(!built); processorPath.addTransitive(jars); return this; } public Builder addApiGeneratingProcessorName(String processor) { Preconditions.checkArgument(!built); apiGeneratingProcessorNames.add(processor); return this; } public Builder addApiGeneratingProcessorPath(NestedSet<Artifact> jars) { Preconditions.checkArgument(!built); apiGeneratingProcessorPath.addTransitive(jars); return this; } public Builder addClassPathResources(List<Artifact> classPathResources) { Preconditions.checkArgument(!built); this.classPathResources.addAll(classPathResources); return this; } public Builder addClassPathResource(Artifact classPathResource) { Preconditions.checkArgument(!built); this.classPathResources.add(classPathResource); return this; } /** * Adds additional outputs to this target's compile action. */ public Builder addAdditionalOutputs(Iterable<Artifact> outputs) { Preconditions.checkArgument(!built); Iterables.addAll(additionalOutputs, outputs); return this; } public JavaTargetAttributes build() { built = true; return new JavaTargetAttributes( sourceFiles, runtimeClassPath, compileTimeClassPath, bootClassPath, sourcePath, nativeLibraries, processorPath.build(), processorNames, processorFlags, apiGeneratingProcessorPath.build(), apiGeneratingProcessorNames, resources, resourceJars.build(), messages, sourceJars, classPathResources, additionalOutputs, directJars.build(), compileTimeDependencyArtifacts.build(), ruleKind, targetLabel, excludedArtifacts, strictJavaDeps); } // TODO(bazel-team): delete the following methods - users should use the built // JavaTargetAttributes instead of accessing mutable state in the Builder. /** @deprecated prefer {@link JavaTargetAttributes#getSourceFiles} */ @Deprecated public Set<Artifact> getSourceFiles() { return sourceFiles; } /** @deprecated prefer {@link JavaTargetAttributes#hasSources} */ @Deprecated public boolean hasSources() { return !sourceFiles.isEmpty() || !sourceJars.isEmpty(); } /** @deprecated prefer {@link JavaTargetAttributes#hasSourceFiles} */ @Deprecated public boolean hasSourceFiles() { return !sourceFiles.isEmpty(); } /** @deprecated prefer {@link JavaTargetAttributes#getInstrumentationMetadata} */ @Deprecated public List<Artifact> getInstrumentationMetadata() { return instrumentationMetadata; } /** @deprecated prefer {@link JavaTargetAttributes#hasSourceJars} */ @Deprecated public boolean hasSourceJars() { return !sourceJars.isEmpty(); } } // // -------------------------- END OF BUILDER CLASS ------------------------- // private final ImmutableSet<Artifact> sourceFiles; private final NestedSet<Artifact> runtimeClassPath; private final NestedSet<Artifact> compileTimeClassPath; private final ImmutableList<Artifact> bootClassPath; private final ImmutableList<Artifact> sourcePath; private final ImmutableList<Artifact> nativeLibraries; private final NestedSet<Artifact> processorPath; private final ImmutableSet<String> processorNames; private final ImmutableSet<String> processorFlags; private final NestedSet<Artifact> apiGeneratingProcessorPath; private final ImmutableSet<String> apiGeneratingProcessorNames; private final ImmutableMap<PathFragment, Artifact> resources; private final NestedSet<Artifact> resourceJars; private final ImmutableList<Artifact> messages; private final ImmutableList<Artifact> sourceJars; private final ImmutableList<Artifact> classPathResources; private final ImmutableSet<Artifact> additionalOutputs; private final NestedSet<Artifact> directJars; private final NestedSet<Artifact> compileTimeDependencyArtifacts; private final String ruleKind; private final Label targetLabel; private final NestedSet<Artifact> excludedArtifacts; private final BuildConfiguration.StrictDepsMode strictJavaDeps; /** Constructor of JavaTargetAttributes. */ private JavaTargetAttributes( Set<Artifact> sourceFiles, NestedSetBuilder<Artifact> runtimeClassPath, NestedSetBuilder<Artifact> compileTimeClassPath, List<Artifact> bootClassPath, List<Artifact> sourcePath, List<Artifact> nativeLibraries, NestedSet<Artifact> processorPath, Set<String> processorNames, Set<String> processorFlags, NestedSet<Artifact> apiGeneratingProcessorPath, Set<String> apiGeneratingProcessorNames, Map<PathFragment, Artifact> resources, NestedSet<Artifact> resourceJars, List<Artifact> messages, List<Artifact> sourceJars, List<Artifact> classPathResources, Set<Artifact> additionalOutputs, NestedSet<Artifact> directJars, NestedSet<Artifact> compileTimeDependencyArtifacts, String ruleKind, Label targetLabel, NestedSetBuilder<Artifact> excludedArtifacts, BuildConfiguration.StrictDepsMode strictJavaDeps) { this.sourceFiles = ImmutableSet.copyOf(sourceFiles); this.runtimeClassPath = runtimeClassPath.build(); this.directJars = directJars; this.compileTimeClassPath = NestedSetBuilder.<Artifact>naiveLinkOrder() .addTransitive(directJars) .addTransitive(compileTimeClassPath.build()) .build(); this.bootClassPath = ImmutableList.copyOf(bootClassPath); this.sourcePath = ImmutableList.copyOf(sourcePath); this.nativeLibraries = ImmutableList.copyOf(nativeLibraries); this.processorPath = processorPath; this.processorNames = ImmutableSet.copyOf(processorNames); this.processorFlags = ImmutableSet.copyOf(processorFlags); this.apiGeneratingProcessorPath = apiGeneratingProcessorPath; this.apiGeneratingProcessorNames = ImmutableSet.copyOf(apiGeneratingProcessorNames); this.resources = ImmutableMap.copyOf(resources); this.resourceJars = resourceJars; this.messages = ImmutableList.copyOf(messages); this.sourceJars = ImmutableList.copyOf(sourceJars); this.classPathResources = ImmutableList.copyOf(classPathResources); this.additionalOutputs = ImmutableSet.copyOf(additionalOutputs); this.compileTimeDependencyArtifacts = compileTimeDependencyArtifacts; this.ruleKind = ruleKind; this.targetLabel = targetLabel; this.excludedArtifacts = excludedArtifacts.build(); this.strictJavaDeps = strictJavaDeps; } public NestedSet<Artifact> getDirectJars() { return directJars; } public NestedSet<Artifact> getCompileTimeDependencyArtifacts() { return compileTimeDependencyArtifacts; } public List<Artifact> getSourceJars() { return sourceJars; } public Map<PathFragment, Artifact> getResources() { return resources; } public NestedSet<Artifact> getResourceJars() { return resourceJars; } public List<Artifact> getMessages() { return messages; } public ImmutableList<Artifact> getClassPathResources() { return classPathResources; } public ImmutableSet<Artifact> getAdditionalOutputs() { return additionalOutputs; } private NestedSet<Artifact> getExcludedArtifacts() { return excludedArtifacts; } /** * Returns the artifacts needed on the runtime classpath of this target. * * See also {@link #getRuntimeClassPathForArchive()}. */ public NestedSet<Artifact> getRuntimeClassPath() { return runtimeClassPath; } /** * Returns the classpath artifacts needed in a deploy jar for this target. * * This excludes the artifacts made available by jars in the deployment * environment. */ public Iterable<Artifact> getRuntimeClassPathForArchive() { Iterable<Artifact> runtimeClasspath = getRuntimeClassPath(); if (getExcludedArtifacts().isEmpty()) { return runtimeClasspath; } else { return Iterables.filter(runtimeClasspath, Predicates.not(Predicates.in(getExcludedArtifacts().toSet()))); } } public NestedSet<Artifact> getCompileTimeClassPath() { return compileTimeClassPath; } public ImmutableList<Artifact> getBootClassPath() { return bootClassPath; } public ImmutableList<Artifact> getSourcePath() { return sourcePath; } public NestedSet<Artifact> getProcessorPath() { return processorPath; } public NestedSet<Artifact> getApiGeneratingProcessorPath() { return apiGeneratingProcessorPath; } public ImmutableSet<String> getApiGeneratingProcessorNames() { return apiGeneratingProcessorNames; } public ImmutableSet<Artifact> getSourceFiles() { return sourceFiles; } public List<Artifact> getNativeLibraries() { return nativeLibraries; } public Collection<String> getProcessorNames() { return processorNames; } public Collection<String> getProcessorFlags() { return processorFlags; } public boolean hasSources() { return !sourceFiles.isEmpty() || !sourceJars.isEmpty(); } public boolean hasResources() { return !resources.isEmpty() || !messages.isEmpty() || !classPathResources.isEmpty() || !resourceJars.isEmpty(); } public boolean hasMessages() { return !messages.isEmpty(); } public String getRuleKind() { return ruleKind; } public Label getTargetLabel() { return targetLabel; } public BuildConfiguration.StrictDepsMode getStrictJavaDeps() { return strictJavaDeps; } }