Fix `equals()` and `hashCode()` for artifacts: artifacts of different classes are not equal.
Also validate that there are no tree and file artifacts with the same exec path.
Fixes #4668.
Closes #5284.
Change-Id: Id97c0407a476a5bfc697b4ca7b858e3d0c0f8c75
PiperOrigin-RevId: 198540425
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
index 95714cd..f18e14f 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
@@ -228,7 +228,7 @@
throw new IllegalArgumentException(
"it is illegal to create an artifact with an empty execPath");
}
- this.hashCode = execPath.hashCode();
+ this.hashCode = execPath.hashCode() + this.getClass().hashCode() * 13;
this.root = root;
this.execPath = execPath;
this.rootRelativePath = rootRelativePath;
@@ -641,6 +641,9 @@
if (!(other instanceof Artifact)) {
return false;
}
+ if (!getClass().equals(other.getClass())) {
+ return false;
+ }
// We don't bother to check root in the equivalence relation, because we
// assume that no root is an ancestor of another one.
Artifact that = (Artifact) other;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
index e417853..ee6063e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
@@ -154,5 +154,11 @@
*/
ImmutableSet<Artifact> getOrphanArtifacts();
+ /**
+ * Returns the set of tree artifacts that have the same exec path as some other artifacts. Should
+ * only be called after the ConfiguredTarget is created.
+ */
+ ImmutableSet<Artifact> getTreeArtifactsConflictingWithFiles();
+
ActionKeyContext getActionKeyContext();
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
index d411250..8a3faea 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
@@ -164,11 +164,45 @@
@Override
public ImmutableSet<Artifact> getOrphanArtifacts() {
if (!allowRegisteringActions) {
- return ImmutableSet.<Artifact>of();
+ return ImmutableSet.of();
}
return ImmutableSet.copyOf(getOrphanArtifactMap().keySet());
}
+ @Override
+ public ImmutableSet<Artifact> getTreeArtifactsConflictingWithFiles() {
+ if (!allowRegisteringActions) {
+ return ImmutableSet.of();
+ }
+
+ boolean hasTreeArtifacts = false;
+ for (Artifact artifact : artifacts.keySet()) {
+ if (artifact.isTreeArtifact()) {
+ hasTreeArtifacts = true;
+ break;
+ }
+ }
+ if (!hasTreeArtifacts) {
+ return ImmutableSet.of();
+ }
+
+ HashSet<PathFragment> collect = new HashSet<>();
+ for (Artifact artifact : artifacts.keySet()) {
+ if (!artifact.isSourceArtifact() && !artifact.isTreeArtifact()) {
+ collect.add(artifact.getExecPath());
+ }
+ }
+
+ ImmutableSet.Builder<Artifact> sameExecPathTreeArtifacts = ImmutableSet.builder();
+ for (Artifact artifact : artifacts.keySet()) {
+ if (artifact.isTreeArtifact() && collect.contains(artifact.getExecPath())) {
+ sameExecPathTreeArtifacts.add(artifact);
+ }
+ }
+
+ return sameExecPathTreeArtifacts.build();
+ }
+
private Map<Artifact, String> getOrphanArtifactMap() {
// Construct this set to avoid poor performance under large --runs_per_test.
Set<Artifact> artifactsWithActions = new HashSet<>();
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index 2d3478a..ca706fc 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -554,7 +554,7 @@
* which this target (which must be an OutputFile or a Rule) is associated.
*/
public Artifact createOutputArtifact() {
- return internalCreateOutputArtifact(getTarget());
+ return internalCreateOutputArtifact(getTarget(), OutputFile.Kind.FILE);
}
/**
@@ -563,7 +563,7 @@
* @see #createOutputArtifact()
*/
public Artifact createOutputArtifact(OutputFile out) {
- return internalCreateOutputArtifact(out);
+ return internalCreateOutputArtifact(out, out.getKind());
}
/**
@@ -572,13 +572,22 @@
* {@link #createOutputArtifact(OutputFile)} can have a more specific
* signature.
*/
- private Artifact internalCreateOutputArtifact(Target target) {
+ private Artifact internalCreateOutputArtifact(Target target, OutputFile.Kind outputFileKind) {
Preconditions.checkState(
target.getLabel().getPackageIdentifier().equals(getLabel().getPackageIdentifier()),
"Creating output artifact for target '%s' in different package than the rule '%s' "
+ "being analyzed", target.getLabel(), getLabel());
ArtifactRoot root = getBinOrGenfilesDirectory();
- return getPackageRelativeArtifact(target.getName(), root);
+ PathFragment packageRelativePath = getPackageDirectory()
+ .getRelative(PathFragment.create(target.getName()));
+ switch (outputFileKind) {
+ case FILE:
+ return getDerivedArtifact(packageRelativePath, root);
+ case FILESET:
+ return getAnalysisEnvironment().getFilesetArtifact(packageRelativePath, root);
+ default:
+ throw new IllegalStateException();
+ }
}
/**
@@ -604,6 +613,14 @@
* Creates an artifact in a directory that is unique to the package that contains the rule, thus
* guaranteeing that it never clashes with artifacts created by rules in other packages.
*/
+ public Artifact getPackageRelativeTreeArtifact(String relative, ArtifactRoot root) {
+ return getPackageRelativeTreeArtifact(PathFragment.create(relative), root);
+ }
+
+ /**
+ * Creates an artifact in a directory that is unique to the package that contains the rule, thus
+ * guaranteeing that it never clashes with artifacts created by rules in other packages.
+ */
public Artifact getBinArtifact(String relative) {
return getBinArtifact(PathFragment.create(relative));
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
index 95e48a3..43de251 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
@@ -13,30 +13,38 @@
// limitations under the License.
package com.google.devtools.build.lib.analysis;
-import com.google.common.base.Function;
import com.google.common.base.Joiner;
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.syntax.EvalException;
-
/**
* Utility class to validate results of executing Skylark rules and aspects.
*/
public class SkylarkProviderValidationUtil {
- public static void checkOrphanArtifacts(RuleContext ruleContext) throws EvalException {
+ public static void validateArtifacts(RuleContext ruleContext) throws EvalException {
+ ImmutableSet<Artifact> treeArtifactsConflictingWithFiles =
+ ruleContext.getAnalysisEnvironment().getTreeArtifactsConflictingWithFiles();
+ if (!treeArtifactsConflictingWithFiles.isEmpty()) {
+ throw new EvalException(
+ null,
+ "The following directories were also declared as files:\n"
+ + artifactsDescription(treeArtifactsConflictingWithFiles));
+ }
+
ImmutableSet<Artifact> orphanArtifacts =
ruleContext.getAnalysisEnvironment().getOrphanArtifacts();
if (!orphanArtifacts.isEmpty()) {
- throw new EvalException(null, "The following files have no generating action:\n"
- + Joiner.on("\n").join(Iterables.transform(orphanArtifacts,
- new Function<Artifact, String>() {
- @Override
- public String apply(Artifact artifact) {
- return artifact.getRootRelativePathString();
- }
- })));
+ throw new EvalException(
+ null,
+ "The following files have no generating action:\n"
+ + artifactsDescription(orphanArtifacts));
}
}
+
+ private static String artifactsDescription(ImmutableSet<Artifact> artifacts) {
+ return Joiner.on("\n")
+ .join(Iterables.transform(artifacts, Artifact::getRootRelativePathString));
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
index d118a4a..a13b281 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
@@ -105,14 +105,22 @@
@Override
public Artifact declareDirectory(String filename, Object sibling) throws EvalException {
context.checkMutable("actions.declare_directory");
+ Artifact result;
if (Runtime.NONE.equals(sibling)) {
- return ruleContext.getPackageRelativeTreeArtifact(
- PathFragment.create(filename), newFileRoot());
+ result =
+ ruleContext.getPackageRelativeTreeArtifact(PathFragment.create(filename), newFileRoot());
} else {
PathFragment original = ((Artifact) sibling).getRootRelativePath();
PathFragment fragment = original.replaceName(filename);
- return ruleContext.getTreeArtifact(fragment, newFileRoot());
+ result = ruleContext.getTreeArtifact(fragment, newFileRoot());
}
+ if (!result.isTreeArtifact()) {
+ throw new EvalException(
+ null,
+ String.format(
+ "'%s' has already been declared as a regular file, not directory.", filename));
+ }
+ return result;
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
index 2c9b62f..1a687c1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
@@ -122,7 +122,7 @@
return null;
}
ConfiguredTarget configuredTarget = createTarget(skylarkRuleContext, target);
- SkylarkProviderValidationUtil.checkOrphanArtifacts(ruleContext);
+ SkylarkProviderValidationUtil.validateArtifacts(ruleContext);
checkDeclaredProviders(configuredTarget, advertisedProviders, location);
return configuredTarget;
} catch (EvalException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/OutputFile.java b/src/main/java/com/google/devtools/build/lib/packages/OutputFile.java
index 101cb1b..90f6707 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/OutputFile.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/OutputFile.java
@@ -22,14 +22,35 @@
*/
public final class OutputFile extends FileTarget {
+ /**
+ * A kind of output file.
+ *
+ * The FILESET kind is only supported for a non-open-sourced {@code fileset} rule.
+ */
+ public enum Kind {
+ FILE,
+ FILESET
+ }
+
private final Rule generatingRule;
+ private final Kind kind;
/**
* Constructs an output file with the given label, which must be in the given
* package.
*/
OutputFile(Package pkg, Label label, Rule generatingRule) {
+ this(pkg, label, Kind.FILE, generatingRule);
+ }
+
+ /**
+ * Constructs an output file with the given label, which must be in the given
+ * package.
+ */
+ OutputFile(Package pkg, Label label,
+ Kind kind, Rule generatingRule) {
super(pkg, label);
+ this.kind = kind;
this.generatingRule = generatingRule;
}
@@ -50,6 +71,13 @@
return generatingRule;
}
+ /**
+ * Returns the kind of this output file.
+ */
+ public Kind getKind() {
+ return kind;
+ }
+
@Override
public String getTargetKind() {
return targetKind();
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Rule.java b/src/main/java/com/google/devtools/build/lib/packages/Rule.java
index c7cca63..37c8767 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Rule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Rule.java
@@ -607,7 +607,7 @@
reportWarning("target '" + getName() + "' is both a rule and a file; please choose "
+ "another name for the rule", eventHandler);
}
- OutputFile outputFile = new OutputFile(pkg, label, this);
+ OutputFile outputFile = new OutputFile(pkg, label, ruleClass.getOutputFileKind(), this);
outputFilesBuilder.add(outputFile);
return outputFile;
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index 0abecd2..d832468 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -615,6 +615,7 @@
private final Map<String, Attribute> attributes = new LinkedHashMap<>();
private final Set<Label> requiredToolchains = new HashSet<>();
private boolean supportsPlatforms = true;
+ private OutputFile.Kind outputFileKind = OutputFile.Kind.FILE;
/**
* Constructs a new {@code RuleClassBuilder} using all attributes from all
@@ -734,6 +735,7 @@
supportsConstraintChecking,
requiredToolchains,
supportsPlatforms,
+ outputFileKind,
attributes.values());
}
@@ -1080,6 +1082,18 @@
}
/**
+ * Sets the kind of output files this rule creates.
+ * DO NOT USE! This only exists to support the non-open-sourced {@code fileset} rule.
+ * {@see OutputFile.Kind}.
+ */
+ public Builder setOutputFileKind(OutputFile.Kind outputFileKind) {
+ this.outputFileKind = outputFileKind;
+ return this;
+ }
+
+
+
+ /**
* Declares that instances of this rule are compatible with the specified environments,
* in addition to the defaults declared by their environment groups. This can be overridden
* by rule-specific declarations. See
@@ -1264,6 +1278,7 @@
@Nullable private final Label ruleDefinitionEnvironmentLabel;
@Nullable private final String ruleDefinitionEnvironmentHashCode;
+ private final OutputFile.Kind outputFileKind;
/**
* The set of configuration fragments which are legal for this rule's implementation to access.
@@ -1328,6 +1343,7 @@
boolean supportsConstraintChecking,
Set<Label> requiredToolchains,
boolean supportsPlatforms,
+ OutputFile.Kind outputFileKind,
Collection<Attribute> attributes) {
this.name = name;
this.key = key;
@@ -1350,6 +1366,7 @@
this.optionReferenceFunction = optionReferenceFunction;
this.ruleDefinitionEnvironmentLabel = ruleDefinitionEnvironmentLabel;
this.ruleDefinitionEnvironmentHashCode = ruleDefinitionEnvironmentHashCode;
+ this.outputFileKind = outputFileKind;
validateNoClashInPublicNames(attributes);
this.attributes = ImmutableList.copyOf(attributes);
this.workspaceOnly = workspaceOnly;
@@ -2174,6 +2191,11 @@
return supportsPlatforms;
}
+ @Nullable
+ public OutputFile.Kind getOutputFileKind() {
+ return outputFileKind;
+ }
+
public static boolean isThirdPartyPackage(PackageIdentifier packageIdentifier) {
if (!packageIdentifier.getRepository().isMain()) {
return false;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
index fad7c84..fa0eefb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
@@ -146,7 +146,7 @@
}
ConfiguredAspect configuredAspect = builder.build();
- SkylarkProviderValidationUtil.checkOrphanArtifacts(ruleContext);
+ SkylarkProviderValidationUtil.validateArtifacts(ruleContext);
return configuredAspect;
}