Skip to content

Commit

Permalink
Add rctx.watch_tree() to watch a directory tree
Browse files Browse the repository at this point in the history
- Added `rctx.watch_tree()` to watch a directory tree, which includes all transitive descendants' names, and if they're files, their contents.
  - In the future we could add glob patterns to this method.
- Added a new SkyFunction DirectoryTreeDigestFunction to do the heavy lifting.
  - In the future, for performance, we could try to get this skyfunction to have a mode where it only digests stat(), to use as heuristics (similar to #21044)

Work towards #20952.
  • Loading branch information
Wyverald committed Feb 15, 2024
1 parent 551f437 commit 5922093
Show file tree
Hide file tree
Showing 13 changed files with 660 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/shell",
"//src/main/java/com/google/devtools/build/lib/skyframe:action_environment_function",
"//src/main/java/com/google/devtools/build/lib/skyframe:directory_listing_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:directory_tree_digest_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:ignored_package_prefixes_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:precomputed_value",
"//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/repository",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,16 @@
import com.google.devtools.build.lib.repository.RepositoryFetchProgress;
import com.google.devtools.build.lib.rules.repository.NeedsSkyframeRestartException;
import com.google.devtools.build.lib.rules.repository.RepoRecordedInput;
import com.google.devtools.build.lib.rules.repository.RepoRecordedInput.Dirents;
import com.google.devtools.build.lib.rules.repository.RepoRecordedInput.RepoCacheFriendlyPath;
import com.google.devtools.build.lib.rules.repository.RepositoryFunction.RepositoryFunctionException;
import com.google.devtools.build.lib.rules.repository.WorkspaceAttributeMapper;
import com.google.devtools.build.lib.runtime.ProcessWrapper;
import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutor;
import com.google.devtools.build.lib.skyframe.DirectoryListingValue;
import com.google.devtools.build.lib.skyframe.DirectoryTreeDigestValue;
import com.google.devtools.build.lib.util.StringUtilities;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.vfs.SyscallCache;
import com.google.devtools.build.skyframe.SkyFunction.Environment;
Expand Down Expand Up @@ -84,6 +83,7 @@ public class StarlarkRepositoryContext extends StarlarkBaseExternalContext {
private final StructImpl attrObject;
private final ImmutableSet<PathFragment> ignoredPatterns;
private final SyscallCache syscallCache;
private final HashMap<RepoRecordedInput.DirTree, String> recordedDirTreeInputs = new HashMap<>();

/**
* Create a new context (repository_ctx) object for a Starlark repository rule ({@code rule}
Expand Down Expand Up @@ -137,6 +137,10 @@ protected String getIdentifyingStringForLogging() {
return RepositoryFetchProgress.repositoryFetchContextString(repoName);
}

public ImmutableMap<RepoRecordedInput.DirTree, String> getRecordedDirTreeInputs() {
return ImmutableMap.copyOf(recordedDirTreeInputs);
}

@StarlarkMethod(
name = "name",
structField = true,
Expand Down Expand Up @@ -572,6 +576,54 @@ public void extract(
env.getListener().post(new ExtractProgress(outputPath.getPath().toString()));
}

@StarlarkMethod(
name = "watch_tree",
doc =
"Tells Bazel to watch for changes to any files or directories transitively under the "
+ "given path. Any changes to the contents of files, the existence of files or "
+ "directories, file names or directory names, will cause this repo to be "
+ "refetched.<p>Note that attempting to watch paths inside the repo currently being "
+ "fetched will result in an error. ",
parameters = {
@Param(
name = "path",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = Label.class),
@ParamType(type = StarlarkPath.class)
},
doc = "path of the directory tree to watch."),
})
public void watchTree(Object path)
throws EvalException, InterruptedException, RepositoryFunctionException {
StarlarkPath p = getPath("watch_tree()", path);
if (!p.isDir()) {
throw Starlark.errorf("can't call watch_tree() on non-directory %s", p);
}
RepoCacheFriendlyPath repoCacheFriendlyPath =
toRepoCacheFriendlyPath(p.getPath(), ShouldWatch.YES);
if (repoCacheFriendlyPath == null) {
return;
}
RootedPath rootedPath = repoCacheFriendlyPath.getRootedPath(env, directories);
if (rootedPath == null) {
throw new NeedsSkyframeRestartException();
}
try {
DirectoryTreeDigestValue digestValue =
(DirectoryTreeDigestValue)
env.getValueOrThrow(DirectoryTreeDigestValue.key(rootedPath), IOException.class);
if (digestValue == null) {
throw new NeedsSkyframeRestartException();
}

recordedDirTreeInputs.put(
new RepoRecordedInput.DirTree(repoCacheFriendlyPath), digestValue.hexDigest());
} catch (IOException e) {
throw new RepositoryFunctionException(e, Transience.TRANSIENT);
}
}

@Override
public String toString() {
return "repository_ctx[" + rule.getLabel() + "]";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ private RepositoryDirectoryValue.Builder fetchInternal(
// Modify marker data to include the files/dirents used by the rule's implementation function.
recordedInputValues.putAll(starlarkRepositoryContext.getRecordedFileInputs());
recordedInputValues.putAll(starlarkRepositoryContext.getRecordedDirentsInputs());
recordedInputValues.putAll(starlarkRepositoryContext.getRecordedDirTreeInputs());

// Ditto for environment variables accessed via `getenv`.
for (String envKey : starlarkRepositoryContext.getAccumulatedEnvKeys()) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/google/devtools/build/lib/rules/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/skyframe:action_environment_function",
"//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:directory_listing_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:directory_tree_digest_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:precomputed_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:repository_mapping_value",
"//src/main/java/com/google/devtools/build/lib/util",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.devtools.build.lib.skyframe.ActionEnvironmentFunction;
import com.google.devtools.build.lib.skyframe.ClientEnvironmentValue;
import com.google.devtools.build.lib.skyframe.DirectoryListingValue;
import com.google.devtools.build.lib.skyframe.DirectoryTreeDigestValue;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.RepositoryMappingValue;
import com.google.devtools.build.lib.util.Fingerprint;
Expand Down Expand Up @@ -92,7 +93,9 @@ public abstract static class Parser {
public static RepoRecordedInput parse(String s) {
List<String> parts = Splitter.on(':').limit(2).splitToList(s);
for (Parser parser :
new Parser[] {File.PARSER, Dirents.PARSER, EnvVar.PARSER, RecordedRepoMapping.PARSER}) {
new Parser[] {
File.PARSER, Dirents.PARSER, DirTree.PARSER, EnvVar.PARSER, RecordedRepoMapping.PARSER
}) {
if (parts.get(0).equals(parser.getPrefix())) {
return parser.parse(parts.get(1));
}
Expand All @@ -112,7 +115,7 @@ public static boolean areAllValuesUpToDate(
throws InterruptedException {
env.getValuesAndExceptions(
recordedInputValues.keySet().stream()
.map(rri -> rri.getSkyKey(directories))
.map(RepoRecordedInput::getSkyKey)
.filter(Objects::nonNull)
.collect(toImmutableSet()));
if (env.valuesMissing()) {
Expand Down Expand Up @@ -159,13 +162,13 @@ public int compareTo(RepoRecordedInput o) {
* no SkyKey is needed.
*/
@Nullable
public abstract SkyKey getSkyKey(BlazeDirectories directories);
public abstract SkyKey getSkyKey();

/**
* Returns whether the given {@code oldValue} is still up-to-date for this recorded input. This
* method can assume that {@link #getSkyKey(BlazeDirectories)} is already evaluated; it can
* request further Skyframe evaluations, and if any values are missing, this method can return any
* value (doesn't matter what) and will be reinvoked after a Skyframe restart.
* method can assume that {@link #getSkyKey()} is already evaluated; it can request further
* Skyframe evaluations, and if any values are missing, this method can return any value (doesn't
* matter what) and will be reinvoked after a Skyframe restart.
*/
public abstract boolean isUpToDate(
Environment env, BlazeDirectories directories, @Nullable String oldValue)
Expand Down Expand Up @@ -330,7 +333,7 @@ public static String fileValueToMarkerValue(FileValue fileValue) throws IOExcept

@Override
@Nullable
public SkyKey getSkyKey(BlazeDirectories directories) {
public SkyKey getSkyKey() {
return path.getRepoDirSkyKeyOrNull();
}

Expand Down Expand Up @@ -405,7 +408,7 @@ public Parser getParser() {

@Nullable
@Override
public SkyKey getSkyKey(BlazeDirectories directories) {
public SkyKey getSkyKey() {
return path.getRepoDirSkyKeyOrNull();
}

Expand Down Expand Up @@ -438,6 +441,81 @@ public static String getDirentsMarkerValue(Path path) throws IOException {
}
}

/**
* Represents an entire directory tree accessed during the fetch. Anything under the tree changing
* (including adding/removing/renaming files or directories and changing file contents) will
* cause it to go out of date.
*/
public static final class DirTree extends RepoRecordedInput {
public static final Parser PARSER =
new Parser() {
@Override
public String getPrefix() {
return "DIRTREE";
}

@Override
public RepoRecordedInput parse(String s) {
return new DirTree(RepoCacheFriendlyPath.parse(s));
}
};

private final RepoCacheFriendlyPath path;

public DirTree(RepoCacheFriendlyPath path) {
this.path = path;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DirTree)) {
return false;
}
DirTree that = (DirTree) o;
return Objects.equals(path, that.path);
}

@Override
public int hashCode() {
return path.hashCode();
}

@Override
public String toStringInternal() {
return path.toString();
}

@Override
public Parser getParser() {
return PARSER;
}

@Nullable
@Override
public SkyKey getSkyKey() {
return path.getRepoDirSkyKeyOrNull();
}

@Override
public boolean isUpToDate(
Environment env, BlazeDirectories directories, @Nullable String oldValue)
throws InterruptedException {
RootedPath rootedPath = path.getRootedPath(env, directories);
if (rootedPath == null) {
return false;
}
DirectoryTreeDigestValue value =
(DirectoryTreeDigestValue) env.getValue(DirectoryTreeDigestValue.key(rootedPath));
if (value == null) {
return false;
}
return oldValue.equals(value.hexDigest());
}
}

/** Represents an environment variable accessed during the repo fetch. */
public static final class EnvVar extends RepoRecordedInput {
static final Parser PARSER =
Expand Down Expand Up @@ -487,7 +565,7 @@ public String toStringInternal() {
}

@Override
public SkyKey getSkyKey(BlazeDirectories directories) {
public SkyKey getSkyKey() {
return ActionEnvironmentFunction.key(name);
}

Expand All @@ -497,7 +575,7 @@ public boolean isUpToDate(
throws InterruptedException {
String v = PrecomputedValue.REPO_ENV.get(env).get(name);
if (v == null) {
v = ((ClientEnvironmentValue) env.getValue(getSkyKey(directories))).getValue();
v = ((ClientEnvironmentValue) env.getValue(getSkyKey())).getValue();
}
// Note that `oldValue` can be null if the env var was not set.
return Objects.equals(oldValue, v);
Expand Down Expand Up @@ -558,16 +636,15 @@ public String toStringInternal() {
}

@Override
public SkyKey getSkyKey(BlazeDirectories directories) {
public SkyKey getSkyKey() {
return RepositoryMappingValue.key(sourceRepo);
}

@Override
public boolean isUpToDate(
Environment env, BlazeDirectories directories, @Nullable String oldValue)
throws InterruptedException {
RepositoryMappingValue repoMappingValue =
(RepositoryMappingValue) env.getValue(getSkyKey(directories));
RepositoryMappingValue repoMappingValue = (RepositoryMappingValue) env.getValue(getSkyKey());
return repoMappingValue != RepositoryMappingValue.NOT_FOUND_VALUE
&& RepositoryName.createUnvalidated(oldValue)
.equals(repoMappingValue.getRepositoryMapping().get(apparentName));
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/google/devtools/build/lib/skyframe/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ java_library(
":diff_awareness_manager",
":directory_listing_function",
":directory_listing_state_value",
":directory_tree_digest_function",
":ephemeral_check_if_output_consumed",
":exclusive_test_build_driver_value",
":execution_finished_event",
Expand Down Expand Up @@ -1248,6 +1249,34 @@ java_library(
],
)

java_library(
name = "directory_tree_digest_function",
srcs = ["DirectoryTreeDigestFunction.java"],
deps = [
":directory_listing_value",
":directory_tree_digest_value",
":dirents",
"//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
"//src/main/java/com/google/devtools/build/lib/util",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//third_party:guava",
"//third_party:jsr305",
],
)

java_library(
name = "directory_tree_digest_value",
srcs = ["DirectoryTreeDigestValue.java"],
deps = [
":sky_functions",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//third_party:auto_value",
],
)

java_library(
name = "dirents",
srcs = ["Dirents.java"],
Expand Down
Loading

0 comments on commit 5922093

Please sign in to comment.