Let artifacts declare that they use content-based paths.
This is basic infrastructure for supporting --experiment_output_paths=content. This change does not implement any actual uses.
The intention is that we can incrementally opt in content-mapping support, i.e. rules can declare which of their outputs should do content mapping. The first test goal will be to opt in java_library compilation.
"Experimental Content-Based Output Paths" design: https://docs.google.com/document/d/17snvmic26-QdGuwVw55Gl0oOufw9sCVuOAvHqGZJFr4/edit
See https://github.com/bazelbuild/bazel/issues/8339
PiperOrigin-RevId: 252148134
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionInput.java b/src/main/java/com/google/devtools/build/lib/actions/ActionInput.java
index 0eb1853..df5fca7 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionInput.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionInput.java
@@ -40,4 +40,12 @@
* @return the relative path to the input file.
*/
PathFragment getExecPath();
+
+ /**
+ * Returns if this input's file system path includes a digest of its content. See {@link
+ * com.google.devtools.build.lib.analysis.config.BuildConfiguration#useContentBasedOutputPaths}.
+ */
+ default boolean contentBasedPath() {
+ return false;
+ }
}
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 66abc0b..a067437 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
@@ -226,7 +226,15 @@
private final ArtifactRoot root;
private final PathFragment execPath;
- private Artifact(ArtifactRoot root, PathFragment execPath) {
+ /**
+ * Content-based output paths are experimental. Only derived artifacts that are explicitly opted
+ * in by their creating rules should use them and only when {@link
+ * com.google.devtools.build.lib.analysis.config.BuildConfiguration#useContentBasedOutputPaths} is
+ * on.
+ */
+ private final boolean contentBasedPath;
+
+ private Artifact(ArtifactRoot root, PathFragment execPath, boolean contentBasedPath) {
Preconditions.checkNotNull(root);
if (execPath.isEmpty()) {
throw new IllegalArgumentException(
@@ -238,6 +246,7 @@
this.hashCode = execPath.hashCode();
this.root = root;
this.execPath = execPath;
+ this.contentBasedPath = contentBasedPath;
}
/** An artifact corresponding to a file in the output tree, generated by an {@link Action}. */
@@ -256,9 +265,19 @@
*/
private Object owner;
- @VisibleForTesting
+ /** Standard constructor for derived artifacts. */
public DerivedArtifact(ArtifactRoot root, PathFragment execPath, ActionLookupKey owner) {
- super(root, execPath);
+ this(root, execPath, owner, /*contentBasedPath=*/ false);
+ }
+
+ /**
+ * Same as {@link #DerivedArtifact(ArtifactRoot, PathFragment, ActionLookupKey)} but includes
+ * tge option to use a content-based path for this artifact (see {@link
+ * com.google.devtools.build.lib.analysis.config.BuildConfiguration#useContentBasedOutputPaths}).
+ */
+ public DerivedArtifact(
+ ArtifactRoot root, PathFragment execPath, ActionLookupKey owner, boolean contentBasedPath) {
+ super(root, execPath, contentBasedPath);
Preconditions.checkState(
!root.getExecPath().isEmpty(), "Derived root has no exec path: %s, %s", root, execPath);
this.rootRelativePath = execPath.relativeTo(root.getExecPath());
@@ -357,7 +376,8 @@
new DerivedArtifact(
root,
rootExecPath.getRelative(rootRelativePath),
- generatingActionKey.getActionLookupKey());
+ generatingActionKey.getActionLookupKey(),
+ /*contentBasedPath=*/ false);
artifact.setGeneratingActionKey(generatingActionKey);
return INTERNER.intern(artifact);
}
@@ -456,6 +476,11 @@
return execPath;
}
+ @Override
+ public boolean contentBasedPath() {
+ return contentBasedPath;
+ }
+
/**
* Returns the path of this Artifact relative to this containing Artifact. Since
* ordinary Artifacts correspond to only one Artifact -- itself -- for ordinary Artifacts,
@@ -536,7 +561,7 @@
@VisibleForTesting
public SourceArtifact(ArtifactRoot root, PathFragment execPath, ArtifactOwner owner) {
- super(root, execPath);
+ super(root, execPath, /*contentBasedPath=*/ false);
this.owner = owner;
}
@@ -596,7 +621,7 @@
@VisibleForTesting
public SpecialArtifact(
ArtifactRoot root, PathFragment execPath, ActionLookupKey owner, SpecialArtifactType type) {
- super(root, execPath, owner);
+ super(root, execPath, owner, /*contentBasedPath=*/ false);
this.type = type;
}
@@ -690,7 +715,8 @@
super(
parentTreeArtifact.getRoot(),
parentTreeArtifact.getExecPath().getRelative(parentRelativePath),
- owner);
+ owner,
+ /*contentBasedPath=*/ false);
Preconditions.checkArgument(
parentTreeArtifact.isTreeArtifact(),
"The parent of TreeFileArtifact (parent-relative path: %s) is not a TreeArtifact: %s",
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
index c565492..dcc4ccd 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
@@ -186,7 +186,8 @@
sourceArtifactRoots),
execPath,
owner,
- null);
+ null,
+ /*contentBasedPath=*/ false);
}
@Override
@@ -227,9 +228,23 @@
// TODO(bazel-team): Don't allow root == execRootParent.
public Artifact.DerivedArtifact getDerivedArtifact(
PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
+ return getDerivedArtifact(rootRelativePath, root, owner, /*contentBasedPath=*/ false);
+ }
+
+ /**
+ * Same as {@link #getDerivedArtifact(PathFragment, ArtifactRoot, ArtifactOwner)} but includes the
+ * option to use a content-based path for this artifact (see {@link
+ * com.google.devtools.build.lib.analysis.config.BuildConfiguration#useContentBasedOutputPaths}).
+ */
+ public Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath,
+ ArtifactRoot root,
+ ArtifactOwner owner,
+ boolean contentBasedPath) {
validatePath(rootRelativePath, root);
return (Artifact.DerivedArtifact)
- getArtifact(root, root.getExecPath().getRelative(rootRelativePath), owner, null);
+ getArtifact(
+ root, root.getExecPath().getRelative(rootRelativePath), owner, null, contentBasedPath);
}
/**
@@ -248,7 +263,8 @@
root,
root.getExecPath().getRelative(rootRelativePath),
owner,
- SpecialArtifactType.FILESET);
+ SpecialArtifactType.FILESET,
+ /*contentBasedPath=*/ false);
}
/**
@@ -266,7 +282,8 @@
root,
root.getExecPath().getRelative(rootRelativePath),
owner,
- SpecialArtifactType.TREE);
+ SpecialArtifactType.TREE,
+ /*contentBasedPath=*/ false);
}
public Artifact.DerivedArtifact getConstantMetadataArtifact(
@@ -277,7 +294,8 @@
root,
root.getExecPath().getRelative(rootRelativePath),
owner,
- SpecialArtifactType.CONSTANT_METADATA);
+ SpecialArtifactType.CONSTANT_METADATA,
+ /*contentBasedPath=*/ false);
}
/**
@@ -288,12 +306,13 @@
ArtifactRoot root,
PathFragment execPath,
ArtifactOwner owner,
- @Nullable SpecialArtifactType type) {
+ @Nullable SpecialArtifactType type,
+ boolean contentBasedPath) {
Preconditions.checkNotNull(root);
Preconditions.checkNotNull(execPath);
if (!root.isSourceRoot()) {
- return createArtifact(root, execPath, owner, type);
+ return createArtifact(root, execPath, owner, type, contentBasedPath);
}
// Double-checked locking to avoid locking cost when possible.
@@ -307,7 +326,9 @@
// There really should be a safety net that makes it impossible to create two Artifacts
// with the same exec path but a different Owner, but we also need to reuse Artifacts from
// previous builds.
- artifact = (SourceArtifact) createArtifact(root, execPath, owner, type);
+ artifact =
+ (SourceArtifact)
+ createArtifact(root, execPath, owner, type, /*contentBasedPath=*/ false);
sourceArtifactCache.putArtifact(execPath, artifact);
}
} finally {
@@ -321,12 +342,13 @@
ArtifactRoot root,
PathFragment execPath,
ArtifactOwner owner,
- @Nullable SpecialArtifactType type) {
+ @Nullable SpecialArtifactType type,
+ boolean contentBasedPath) {
Preconditions.checkNotNull(owner);
if (type == null) {
return root.isSourceRoot()
? new Artifact.SourceArtifact(root, execPath, owner)
- : new Artifact.DerivedArtifact(root, execPath, (ActionLookupKey) owner);
+ : new Artifact.DerivedArtifact(root, execPath, (ActionLookupKey) owner, contentBasedPath);
} else {
return new Artifact.SpecialArtifact(root, execPath, (ActionLookupKey) owner, type);
}
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 2f69670..a72f443 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
@@ -67,6 +67,14 @@
Artifact.DerivedArtifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root);
/**
+ * Same as {@link #getDerivedArtifact(PathFragment, ArtifactRoot)} but includes the option to use
+ * a content-based path for this artifact (see {@link
+ * BuildConfiguration#useContentBasedOutputPaths()}).
+ */
+ Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath);
+
+ /**
* Returns an artifact for the derived file {@code rootRelativePath} whose changes do not cause a
* rebuild.
*
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 b31208d..f6e6f76 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
@@ -282,9 +282,15 @@
@Override
public Artifact.DerivedArtifact getDerivedArtifact(
PathFragment rootRelativePath, ArtifactRoot root) {
+ return getDerivedArtifact(rootRelativePath, root, /*contentBasedPath=*/ false);
+ }
+
+ @Override
+ public Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath) {
Preconditions.checkState(enabled);
return dedupAndTrackArtifactAndOrigin(
- artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner()),
+ artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner(), contentBasedPath),
extendedSanityChecks ? new Throwable() : null);
}
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 b4ab0d7..72549d1 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
@@ -686,7 +686,17 @@
@Override
public Artifact.DerivedArtifact getPackageRelativeArtifact(
PathFragment relative, ArtifactRoot root) {
- return getDerivedArtifact(getPackageDirectory().getRelative(relative), root);
+ return getPackageRelativeArtifact(relative, root, /*contentBasedPath=*/ false);
+ }
+
+ /**
+ * Same as {@link #getPackageRelativeArtifact(PathFragment, ArtifactRoot)} but includes the option
+ * option to use a content-based path for this artifact (see {@link
+ * BuildConfiguration#useContentBasedOutputPaths()}).
+ */
+ private Artifact.DerivedArtifact getPackageRelativeArtifact(
+ PathFragment relative, ArtifactRoot root, boolean contentBasedPath) {
+ return getDerivedArtifact(getPackageDirectory().getRelative(relative), root, contentBasedPath);
}
/**
@@ -694,7 +704,17 @@
* guaranteeing that it never clashes with artifacts created by rules in other packages.
*/
public Artifact getPackageRelativeArtifact(String relative, ArtifactRoot root) {
- return getPackageRelativeArtifact(PathFragment.create(relative), root);
+ return getPackageRelativeArtifact(relative, root, /*contentBasedPath=*/ false);
+ }
+
+ /**
+ * Same as {@link #getPackageRelativeArtifact(String, ArtifactRoot)} but includes the option to
+ * use a content-based path for this artifact (see {@link
+ * BuildConfiguration#useContentBasedOutputPaths()}).
+ */
+ private Artifact getPackageRelativeArtifact(
+ String relative, ArtifactRoot root, boolean contentBasedPath) {
+ return getPackageRelativeArtifact(PathFragment.create(relative), root, contentBasedPath);
}
@Override
@@ -712,10 +732,20 @@
@Override
public Artifact.DerivedArtifact getDerivedArtifact(
PathFragment rootRelativePath, ArtifactRoot root) {
+ return getDerivedArtifact(rootRelativePath, root, /*contentBasedPath=*/ false);
+ }
+
+ /**
+ * Same as {@link #getDerivedArtifact(PathFragment, ArtifactRoot)} but includes the option to use
+ * a content-based path for this artifact (see {@link
+ * BuildConfiguration#useContentBasedOutputPaths()}).
+ */
+ public Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath) {
Preconditions.checkState(rootRelativePath.startsWith(getPackageDirectory()),
"Output artifact '%s' not under package directory '%s' for target '%s'",
rootRelativePath, getPackageDirectory(), getLabel());
- return getAnalysisEnvironment().getDerivedArtifact(rootRelativePath, root);
+ return getAnalysisEnvironment().getDerivedArtifact(rootRelativePath, root, contentBasedPath);
}
@Override
@@ -1351,6 +1381,16 @@
@Override
public Artifact getImplicitOutputArtifact(ImplicitOutputsFunction function)
throws InterruptedException {
+ return getImplicitOutputArtifact(function, /*contentBasedPath=*/ false);
+ }
+
+ /**
+ * Same as {@link #getImplicitOutputArtifact(ImplicitOutputsFunction)} but includes the option to
+ * use a content-based path for this artifact (see {@link
+ * BuildConfiguration#useContentBasedOutputPaths()}).
+ */
+ public Artifact getImplicitOutputArtifact(
+ ImplicitOutputsFunction function, boolean contentBasedPath) throws InterruptedException {
Iterable<String> result;
try {
result =
@@ -1360,14 +1400,23 @@
// It's ok as long as we don't use this method from Skylark.
throw new IllegalStateException(e);
}
- return getImplicitOutputArtifact(Iterables.getOnlyElement(result));
+ return getImplicitOutputArtifact(Iterables.getOnlyElement(result), contentBasedPath);
}
/**
* Only use from Skylark. Returns the implicit output artifact for a given output path.
*/
public Artifact getImplicitOutputArtifact(String path) {
- return getPackageRelativeArtifact(path, getBinOrGenfilesDirectory());
+ return getImplicitOutputArtifact(path, /*contentBasedPath=*/ false);
+ }
+
+ /**
+ * Same as {@link #getImplicitOutputArtifact(String)} but includes the option to use a a
+ * content-based path for this artifact (see {@link
+ * BuildConfiguration#useContentBasedOutputPaths()}).
+ */
+ private Artifact getImplicitOutputArtifact(String path, boolean contentBasedPath) {
+ return getPackageRelativeArtifact(path, getBinOrGenfilesDirectory(), contentBasedPath);
}
/**
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
index 4dd23eb..6e05f61 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
@@ -491,4 +491,18 @@
ArtifactRoot.asSourceRoot(Root.fromPath(scratch.dir("/"))),
scratch.file("/aaa/bbb/ccc/ddd"));
}
+
+ @Test
+ public void canDeclareContentBasedOutput() throws Exception {
+ Path execRoot = scratch.getFileSystem().getPath("/");
+ ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/newRoot"));
+ assertThat(
+ new Artifact.DerivedArtifact(
+ root,
+ PathFragment.create("newRoot/my.output"),
+ ActionsTestUtil.NULL_ARTIFACT_OWNER,
+ /*contentBasedPath=*/ true)
+ .contentBasedPath())
+ .isTrue();
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
index 09caee3..7b5eb43 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
@@ -126,6 +126,12 @@
}
@Override
+ public Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath) {
+ return original.getDerivedArtifact(rootRelativePath, root, contentBasedPath);
+ }
+
+ @Override
public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
return original.getConstantMetadataArtifact(rootRelativePath, root);
}
@@ -371,6 +377,12 @@
}
@Override
+ public Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath) {
+ return null;
+ }
+
+ @Override
public Artifact getStableWorkspaceStatusArtifact() {
return null;
}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index 32824a5..834d8ff 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -2011,6 +2011,12 @@
}
@Override
+ public Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public Artifact getStableWorkspaceStatusArtifact() {
throw new UnsupportedOperationException();
}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
index d4c6d21..86289c3 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
@@ -18,6 +18,7 @@
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -95,6 +96,14 @@
return CppLinkActionTest.this.getDerivedArtifact(
rootRelativePath, root, ActionsTestUtil.NULL_ARTIFACT_OWNER);
}
+
+ @Override
+ public Artifact.DerivedArtifact getDerivedArtifact(
+ PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPaths) {
+ Preconditions.checkArgument(
+ !contentBasedPaths, "C++ tests don't use content-based outputs");
+ return getDerivedArtifact(rootRelativePath, root);
+ }
},
masterConfig);
}