Skip to content

Commit

Permalink
Add managed directories to RepositoryDirectoryValue.
Browse files Browse the repository at this point in the history
- This is only a part of managed directories feature, still not complete.
- In this part the link between WorkspaceFileListener and Bazel-specific structures is to be seen.
- Class ManagedDirectoriesKnowledgeImpl obviously contains logic, needed for the future, not existing yet functionality, but if I only keep the currently needed part, it will not be clear why we need a separate wrapper class at all.

- Keep precalculated (by SequencedSkyframeExecutor) value of managed directories in the holder object, created by BazelRepositoryModule and available to all interested parties (repository dirty checker, RepositoryDelegatorFunction).
- Managed directories are part of the external repository definition. When the set of managed directories for a repository change, repository should be considered dirty. Repository dirty checker is changed to support that.
- When the set of managed directories for a repository change, also the computed repository digest value should change.
Add managed directories paths fragments to the digest.
- Add tests for both dirty checker and digest calculation.

PiperOrigin-RevId: 246370690
  • Loading branch information
Googler authored and copybara-github committed May 2, 2019
1 parent b0c7ee5 commit 2291342
Show file tree
Hide file tree
Showing 17 changed files with 599 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@
import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
import com.google.devtools.build.lib.rules.repository.LocalRepositoryFunction;
import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule;
import com.google.devtools.build.lib.rules.repository.ManagedDirectoriesKnowledgeImpl;
import com.google.devtools.build.lib.rules.repository.NewLocalRepositoryFunction;
import com.google.devtools.build.lib.rules.repository.NewLocalRepositoryRule;
import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryDirtinessChecker;
import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
import com.google.devtools.build.lib.runtime.BlazeModule;
Expand All @@ -67,18 +68,14 @@
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.PrecomputedValue.Injected;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryBootstrap;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.FileSystem;
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.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParsingResult;
import java.io.IOException;
Expand All @@ -87,7 +84,6 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/** Adds support for fetching external code. */
public class BazelRepositoryModule extends BlazeModule {
Expand All @@ -105,10 +101,14 @@ public class BazelRepositoryModule extends BlazeModule {
private final MutableSupplier<Map<String, String>> clientEnvironmentSupplier =
new MutableSupplier<>();
private ImmutableMap<RepositoryName, PathFragment> overrides = ImmutableMap.of();
private Optional<RootedPath> resolvedFile = Optional.<RootedPath>absent();
private Optional<RootedPath> resolvedFileReplacingWorkspace = Optional.<RootedPath>absent();
private Set<String> outputVerificationRules = ImmutableSet.<String>of();
private Optional<RootedPath> resolvedFile = Optional.absent();
private Optional<RootedPath> resolvedFileReplacingWorkspace = Optional.absent();
private Set<String> outputVerificationRules = ImmutableSet.of();
private FileSystem filesystem;
// We hold the precomputed value of the managed directories here, so that the dependency
// on WorkspaceFileValue is not registered for each FileStateValue.
private final ManagedDirectoriesKnowledgeImpl managedDirectoriesKnowledge =
new ManagedDirectoriesKnowledgeImpl();

public BazelRepositoryModule() {
this.skylarkRepositoryFunction = new SkylarkRepositoryFunction(httpDownloader);
Expand All @@ -128,35 +128,6 @@ public static ImmutableMap<String, RepositoryFunction> repositoryRules(
.build();
}

/**
* A dirtiness checker that always dirties {@link RepositoryDirectoryValue}s so that if they were
* produced in a {@code --nofetch} build, they are re-created no subsequent {@code --fetch}
* builds.
*
* <p>The alternative solution would be to reify the value of the flag as a Skyframe value.
*/
private static final SkyValueDirtinessChecker REPOSITORY_VALUE_CHECKER =
new SkyValueDirtinessChecker() {
@Override
public boolean applies(SkyKey skyKey) {
return skyKey.functionName().equals(SkyFunctions.REPOSITORY_DIRECTORY);
}

@Override
public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) {
throw new UnsupportedOperationException();
}

@Override
public DirtyResult check(
SkyKey skyKey, SkyValue skyValue, @Nullable TimestampGranularityMonitor tsgm) {
RepositoryDirectoryValue repositoryValue = (RepositoryDirectoryValue) skyValue;
return repositoryValue.repositoryExists() && repositoryValue.isFetchingDelayed()
? DirtyResult.dirty(skyValue)
: DirtyResult.notDirty(skyValue);
}
};

private static class RepositoryCacheInfoItem extends InfoItem {
private final RepositoryCache repositoryCache;

Expand All @@ -182,17 +153,25 @@ public void serverInit(OptionsParsingResult startupOptions, ServerBuilder builde
@Override
public void workspaceInit(
BlazeRuntime runtime, BlazeDirectories directories, WorkspaceBuilder builder) {
builder.addCustomDirtinessChecker(REPOSITORY_VALUE_CHECKER);
builder.setWorkspaceFileHeaderListener(
value ->
managedDirectoriesKnowledge.setManagedDirectories(
value != null ? value.getManagedDirectories() : ImmutableMap.of()));

RepositoryDirectoryDirtinessChecker customDirtinessChecker =
new RepositoryDirectoryDirtinessChecker(managedDirectoriesKnowledge);
builder.addCustomDirtinessChecker(customDirtinessChecker);
// Create the repository function everything flows through.
builder.addSkyFunction(SkyFunctions.REPOSITORY, new RepositoryLoaderFunction());
builder.addSkyFunction(
SkyFunctions.REPOSITORY_DIRECTORY,
RepositoryDelegatorFunction repositoryDelegatorFunction =
new RepositoryDelegatorFunction(
repositoryHandlers,
skylarkRepositoryFunction,
isFetch,
clientEnvironmentSupplier,
directories));
directories,
managedDirectoriesKnowledge);
builder.addSkyFunction(SkyFunctions.REPOSITORY_DIRECTORY, repositoryDelegatorFunction);
builder.addSkyFunction(MavenServerFunction.NAME, new MavenServerFunction(directories));
filesystem = runtime.getFileSystem();
}
Expand Down Expand Up @@ -233,7 +212,7 @@ public void beforeCommand(CommandEnvironment env) {
env.getReporter()
.handle(
Event.warn(
"Ingoring request to scale timeouts for repositories by a non-positive"
"Ignoring request to scale timeouts for repositories by a non-positive"
+ " factor"));
skylarkRepositoryFunction.setTimeoutScaling(1.0);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2019 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.repository;

import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import javax.annotation.Nullable;

/**
* Interface to access the managed directories holder object.
*
* <p>Managed directories are user-owned directories, which can be incrementally updated by
* repository rules, so that the updated files are visible for the actions in the same build.
*
* <p>Having managed directories as a separate component (and not SkyValue) allows to skip recording
* the dependency in Skyframe for each FileStateValue and DirectoryListingStateValue.
*/
public interface ManagedDirectoriesKnowledge {
ManagedDirectoriesKnowledge NO_MANAGED_DIRECTORIES =
new ManagedDirectoriesKnowledge() {
@Nullable
@Override
public RepositoryName getOwnerRepository(RootedPath rootedPath, boolean old) {
return null;
}

@Override
public ImmutableSet<PathFragment> getManagedDirectories(RepositoryName repositoryName) {
return ImmutableSet.of();
}
};

@Nullable
RepositoryName getOwnerRepository(RootedPath rootedPath, boolean old);

ImmutableSet<PathFragment> getManagedDirectories(RepositoryName repositoryName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2019 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.repository;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import java.util.Comparator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;

/** Managed directories component. {@link ManagedDirectoriesKnowledge} */
public class ManagedDirectoriesKnowledgeImpl implements ManagedDirectoriesKnowledge {
private final AtomicReference<ImmutableSortedMap<PathFragment, RepositoryName>>
managedDirectoriesRef = new AtomicReference<>(ImmutableSortedMap.of());
private final AtomicReference<Map<RepositoryName, ImmutableSet<PathFragment>>> repoToDirMapRef =
new AtomicReference<>(ImmutableMap.of());

/**
* During build commands execution, Skyframe caches the states of files (FileStateValue) and
* directory listings (DirectoryListingStateValue). In the case when the files/directories are
* under a managed directory or inside an external repository, evaluation of file/directory
* listing states requires that the RepositoryDirectoryValue of the owning external repository is
* evaluated beforehand. (So that the repository rule generates the files.) So there is a
* dependency on RepositoryDirectoryValue for files under managed directories and external
* repositories. This dependency is recorded by Skyframe,
*
* <p>From the other side, by default Skyframe injects the new values of changed files already at
* the stage of checking what files have changed. Only the values without any dependencies can be
* injected into Skyframe. Skyframe can be specifically instructed to not inject new values and
* only register them as changed.
*
* <p>When the values of managed directories change, some files appear to become files under
* managed directories, or they are no longer files under managed directories. This implies that
* corresponding file/directory listing states gain the dependency (RepositoryDirectoryValue) or
* they lose this dependency. In both cases, we should prevent Skyframe from injecting those new
* values of file/directory listing states at the stage of checking changed files.
*
* <p>That is why we need to keep track of the previous value of the managed directories.
*/
private final AtomicReference<ImmutableSortedMap<PathFragment, RepositoryName>>
oldManagedDirectoriesRef = new AtomicReference<>(ImmutableSortedMap.of());

@Override
@Nullable
public RepositoryName getOwnerRepository(RootedPath rootedPath, boolean old) {
PathFragment relativePath = rootedPath.getRootRelativePath();
NavigableMap<PathFragment, RepositoryName> map =
old ? oldManagedDirectoriesRef.get() : managedDirectoriesRef.get();
Map.Entry<PathFragment, RepositoryName> entry = map.floorEntry(relativePath);
if (entry != null && relativePath.startsWith(entry.getKey())) {
return entry.getValue();
}
return null;
}

@Override
public ImmutableSet<PathFragment> getManagedDirectories(RepositoryName repositoryName) {
ImmutableSet<PathFragment> pathFragments = repoToDirMapRef.get().get(repositoryName);
return pathFragments != null ? pathFragments : ImmutableSet.of();
}

public void setManagedDirectories(ImmutableMap<PathFragment, RepositoryName> map) {
oldManagedDirectoriesRef.set(managedDirectoriesRef.get());
ImmutableSortedMap<PathFragment, RepositoryName> pathsMap = ImmutableSortedMap.copyOf(map);
managedDirectoriesRef.set(pathsMap);

Map<RepositoryName, Set<PathFragment>> reposMap = Maps.newHashMap();
for (Map.Entry<PathFragment, RepositoryName> entry : pathsMap.entrySet()) {
RepositoryName repositoryName = entry.getValue();
reposMap.computeIfAbsent(repositoryName, name -> Sets.newTreeSet()).add(entry.getKey());
}

ImmutableSortedMap.Builder<RepositoryName, ImmutableSet<PathFragment>> builder =
new ImmutableSortedMap.Builder<>(Comparator.comparing(RepositoryName::getName));
for (Map.Entry<RepositoryName, Set<PathFragment>> entry : reposMap.entrySet()) {
builder.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
}
repoToDirMapRef.set(builder.build());
}
}
Loading

0 comments on commit 2291342

Please sign in to comment.