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/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) {