blob: 9ec8694c68591a5d20d18d72a548256dad821036 [file] [log] [blame]
// 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.query2.testutil;
import static com.google.devtools.build.lib.packages.Rule.ALL_LABELS;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.bazel.bzlmod.BazelLockFileFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleResolutionFunction;
import com.google.devtools.build.lib.bazel.bzlmod.FakeRegistry;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileFunction;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
import com.google.devtools.build.lib.bazel.bzlmod.YankedVersionsUtil;
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.BazelCompatibilityMode;
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.CheckDirectDepsMode;
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.LockfileMode;
import com.google.devtools.build.lib.clock.BlazeClock;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.packages.ConstantRuleVisibility;
import com.google.devtools.build.lib.packages.LabelPrinter;
import com.google.devtools.build.lib.packages.PackageFactory;
import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.packages.util.MockToolsConfig;
import com.google.devtools.build.lib.pkgcache.PackageManager;
import com.google.devtools.build.lib.pkgcache.PackageOptions;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.pkgcache.TargetPatternPreloader;
import com.google.devtools.build.lib.query2.QueryEnvironmentFactory;
import com.google.devtools.build.lib.query2.common.AbstractBlazeQueryEnvironment;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.ThreadSafeMutableSet;
import com.google.devtools.build.lib.query2.engine.QueryEvalResult;
import com.google.devtools.build.lib.query2.engine.QueryException;
import com.google.devtools.build.lib.query2.engine.QueryParser;
import com.google.devtools.build.lib.query2.engine.QuerySyntaxException;
import com.google.devtools.build.lib.query2.engine.QueryUtil;
import com.google.devtools.build.lib.query2.engine.QueryUtil.AggregateAllOutputFormatterCallback;
import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback;
import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
import com.google.devtools.build.lib.skyframe.IgnoredPackagePrefixesFunction;
import com.google.devtools.build.lib.skyframe.PackageValue;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.SkyframeTargetPatternEvaluator;
import com.google.devtools.build.lib.skyframe.packages.PackageFactoryBuilderWithSkyframeForTesting;
import com.google.devtools.build.lib.testing.common.FakeOptions;
import com.google.devtools.build.lib.testutil.SkyframeExecutorTestHelper;
import com.google.devtools.build.lib.testutil.TestPackageFactoryBuilderFactory;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.DelegatingSyscallCache;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
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.SyscallCache;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import com.google.devtools.build.skyframe.MemoizingEvaluator;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.common.options.Options;
import com.google.errorprone.annotations.ForOverride;
import java.io.IOException;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
/** An implementation of AbstractQueryHelper to support testing bazel query. */
public abstract class SkyframeQueryHelper extends AbstractQueryHelper<Target> {
protected SkyframeExecutor skyframeExecutor;
protected FileSystem fileSystem =
new InMemoryFileSystem(BlazeClock.instance(), DigestHashFunction.SHA256);
private FakeRegistry registry;
protected Path rootDirectory;
protected Path outputBase;
protected Path moduleRoot;
protected BlazeDirectories directories;
private RepositoryName toolsRepository;
protected AnalysisMock analysisMock;
private QueryEnvironmentFactory queryEnvironmentFactory;
private PackageManager pkgManager;
private TargetPatternPreloader targetParser;
private boolean blockUniverseEvaluationErrors;
protected final ActionKeyContext actionKeyContext = new ActionKeyContext();
private final PathFragment ignoredPackagePrefixesFile = PathFragment.create("ignored");
private final DelegatingSyscallCache delegatingSyscallCache = new DelegatingSyscallCache();
@Override
public void setUp() throws Exception {
super.setUp();
analysisMock = AnalysisMock.get();
rootDirectory = createDir(getRootDirectoryNameForSetup());
outputBase = createDir(fileSystem.getPath("/output").getPathString());
rootDirectory = createDir(getRootDirectoryNameForSetup());
directories =
new BlazeDirectories(
new ServerDirectories(rootDirectory, outputBase, outputBase),
rootDirectory,
/* defaultSystemJavabase= */ null,
analysisMock.getProductName());
delegatingSyscallCache.setDelegate(SyscallCache.NO_CACHE);
moduleRoot = createDir(outputBase.getRelative("modules").getPathString());
registry = FakeRegistry.DEFAULT_FACTORY.newFakeRegistry(moduleRoot.getPathString());
writeFile("MODULE.bazel", "module( name = \"root\", version = \"1.0\")");
initTargetPatternEvaluator(analysisMock.createRuleClassProvider());
MockToolsConfig mockToolsConfig = new MockToolsConfig(rootDirectory);
analysisMock.setupMockClient(mockToolsConfig);
analysisMock.setupMockWorkspaceFiles(directories.getEmbeddedBinariesRoot());
analysisMock.setupMockToolsRepository(mockToolsConfig);
analysisMock.ccSupport().setup(mockToolsConfig);
analysisMock.pySupport().setup(mockToolsConfig);
performAdditionalClientSetup(mockToolsConfig);
this.queryEnvironmentFactory = makeQueryEnvironmentFactory();
}
protected abstract String getRootDirectoryNameForSetup();
protected abstract void performAdditionalClientSetup(MockToolsConfig mockToolsConfig)
throws IOException;
protected Path createDir(String pathName) throws IOException {
Path dir = fileSystem.getPath(pathName);
dir.createDirectoryAndParents();
return dir;
}
@Override
public PathFragment getIgnoredPackagePrefixesFile() {
return ignoredPackagePrefixesFile;
}
@Override
public void setBlockUniverseEvaluationErrors(boolean blockUniverseEvaluationErrors) {
if (this.blockUniverseEvaluationErrors == blockUniverseEvaluationErrors) {
return;
}
this.blockUniverseEvaluationErrors = blockUniverseEvaluationErrors;
}
@ForOverride
protected QueryEnvironmentFactory makeQueryEnvironmentFactory() {
return new QueryEnvironmentFactory();
}
@Override
public Path getRootDirectory() {
return rootDirectory;
}
@Override
public void clearAllFiles() throws IOException {
rootDirectory.deleteTreesBelow();
}
@Override
public void writeFile(String fileName, String... lines) throws IOException {
Path file = rootDirectory.getRelative(fileName);
if (file.exists()) {
throw new IOException("Could not create scratch file (file exists) " + fileName);
}
file.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(file, Joiner.on('\n').join(lines));
}
@Override
public void overwriteFile(String fileName, String... lines) throws IOException {
Path file = rootDirectory.getRelative(fileName);
file.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(file, Joiner.on('\n').join(lines));
}
@Override
public void ensureSymbolicLink(String link, String target) throws IOException {
Path linkPath = rootDirectory.getRelative(link);
Path targetPath = rootDirectory.getRelative(target);
linkPath.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.ensureSymbolicLink(linkPath, targetPath);
}
@Override
public AbstractBlazeQueryEnvironment<Target> getQueryEnvironment() {
return queryEnvironmentFactory.create(
skyframeExecutor.getQueryTransitivePackagePreloader(),
skyframeExecutor,
pkgManager,
pkgManager,
targetParser,
mainRepoTargetParser,
/* relativeWorkingDirectory= */ PathFragment.EMPTY_FRAGMENT,
keepGoing,
/* strictScope= */ true,
orderedResults,
universeScope,
/* loadingPhaseThreads= */ 1,
/* labelFilter= */ ALL_LABELS,
getReporter(),
this.settings,
getExtraQueryFunctions(),
pkgManager.getPackagePath(),
blockUniverseEvaluationErrors,
/* useGraphlessQuery= */ false,
LabelPrinter.legacy());
}
protected abstract Iterable<QueryFunction> getExtraQueryFunctions();
@Override
public ResultAndTargets<Target> evaluateQuery(String query)
throws QueryException, InterruptedException {
try (AbstractBlazeQueryEnvironment<Target> env = getQueryEnvironment()) {
return evaluateQuery(query, env);
}
}
public static ResultAndTargets<Target> evaluateQuery(
String query, AbstractBlazeQueryEnvironment<Target> env)
throws QueryException, InterruptedException {
AggregateAllOutputFormatterCallback<Target, ?> callback =
QueryUtil.newOrderedAggregateAllOutputFormatterCallback(env);
QueryEvalResult queryEvalResult;
try {
queryEvalResult =
env.evaluateQuery(env.transformParsedQuery(QueryParser.parse(query, env)), callback);
} catch (IOException e) {
// Should be impossible since AggregateAllOutputFormatterCallback doesn't throw IOException.
throw new IllegalStateException(e);
} catch (QuerySyntaxException e) {
// Expect valid query syntax in tests.
throw new IllegalArgumentException(e);
}
return new ResultAndTargets<>(
queryEvalResult, new OrderedThreadSafeImmutableSet(env, callback.getResult()));
}
@Override
public Set<Target> evaluateQueryRaw(String query) throws QueryException, InterruptedException {
Set<Target> result = new LinkedHashSet<>();
ThreadSafeOutputFormatterCallback<Target> callback =
new ThreadSafeOutputFormatterCallback<>() {
@Override
public synchronized void processOutput(Iterable<Target> partialResult) {
Iterables.addAll(result, partialResult);
}
};
try (AbstractBlazeQueryEnvironment<Target> env = getQueryEnvironment()) {
try {
env.evaluateQuery(env.transformParsedQuery(QueryParser.parse(query, env)), callback);
} catch (IOException e) {
// Should be impossible since the callback we passed in above doesn't throw IOException.
throw new IllegalStateException(e);
} catch (QuerySyntaxException e) {
// Expect valid query syntax in tests.
throw new IllegalArgumentException(e);
}
}
return result;
}
@Override
public RepositoryName getToolsRepository() {
return toolsRepository;
}
@Override
public String getLabel(Target target) {
return target.getLabel().toString();
}
@Override
public void addModule(ModuleKey key, String... moduleFileLines) {
registry.addModule(key, moduleFileLines);
}
protected boolean enableBzlmod() {
return true;
}
protected void initTargetPatternEvaluator(ConfiguredRuleClassProvider ruleClassProvider) {
this.toolsRepository = ruleClassProvider.getToolsRepository();
skyframeExecutor = createSkyframeExecutor(ruleClassProvider);
PackageOptions packageOptions = Options.getDefaults(PackageOptions.class);
packageOptions.defaultVisibility = ConstantRuleVisibility.PRIVATE;
packageOptions.showLoadingProgress = true;
packageOptions.globbingThreads = 7;
packageOptions.packagePath = ImmutableList.of(rootDirectory.getPathString());
BuildLanguageOptions buildLanguageOptions = Options.getDefaults(BuildLanguageOptions.class);
buildLanguageOptions.enableBzlmod = enableBzlmod();
PathPackageLocator packageLocator =
skyframeExecutor.createPackageLocator(
getReporter(), packageOptions.packagePath, rootDirectory);
try {
skyframeExecutor.sync(
getReporter(),
packageLocator,
UUID.randomUUID(),
ImmutableMap.of(),
ImmutableMap.of(),
new TimestampGranularityMonitor(BlazeClock.instance()),
FakeOptions.builder().put(packageOptions).put(buildLanguageOptions).build());
} catch (InterruptedException | AbruptExitException e) {
throw new IllegalStateException(e);
}
pkgManager = skyframeExecutor.getPackageManager();
targetParser = new SkyframeTargetPatternEvaluator(skyframeExecutor);
}
@Override
public void useRuleClassProvider(ConfiguredRuleClassProvider ruleClassProvider) {
initTargetPatternEvaluator(ruleClassProvider);
}
public void setSyscallCache(SyscallCache syscallCache) {
this.delegatingSyscallCache.setDelegate(syscallCache);
}
protected SkyframeExecutor createSkyframeExecutor(ConfiguredRuleClassProvider ruleClassProvider) {
PackageFactory pkgFactory =
((PackageFactoryBuilderWithSkyframeForTesting)
TestPackageFactoryBuilderFactory.getInstance().builder(directories))
.setExtraSkyFunctions(analysisMock.getSkyFunctions(directories))
.setExtraPrecomputeValues(
ImmutableList.of(
PrecomputedValue.injected(
ModuleFileFunction.MODULE_OVERRIDES, ImmutableMap.of()),
PrecomputedValue.injected(
ModuleFileFunction.REGISTRIES, ImmutableList.of(registry.getUrl())),
PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false),
PrecomputedValue.injected(
BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES,
CheckDirectDepsMode.WARNING),
PrecomputedValue.injected(
YankedVersionsUtil.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
PrecomputedValue.injected(
BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE,
BazelCompatibilityMode.ERROR),
PrecomputedValue.injected(
BazelLockFileFunction.LOCKFILE_MODE, LockfileMode.OFF)))
.setEnvironmentExtensions(getEnvironmentExtensions())
.build(ruleClassProvider, fileSystem);
SkyframeExecutor skyframeExecutor =
BazelSkyframeExecutorConstants.newBazelSkyframeExecutorBuilder()
.setPkgFactory(pkgFactory)
.setFileSystem(fileSystem)
.setDirectories(directories)
.setActionKeyContext(actionKeyContext)
.setIgnoredPackagePrefixesFunction(
new IgnoredPackagePrefixesFunction(ignoredPackagePrefixesFile))
.setExtraSkyFunctions(analysisMock.getSkyFunctions(directories))
.setPerCommandSyscallCache(delegatingSyscallCache)
.build();
skyframeExecutor.injectExtraPrecomputedValues(
ImmutableList.<PrecomputedValue.Injected>builder()
.add(
PrecomputedValue.injected(
RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE,
Optional.empty()))
.add(
PrecomputedValue.injected(
RepositoryDelegatorFunction.REPOSITORY_OVERRIDES, ImmutableMap.of()))
.add(PrecomputedValue.injected(ModuleFileFunction.MODULE_OVERRIDES, ImmutableMap.of()))
.add(
PrecomputedValue.injected(
RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING,
RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY))
.add(
PrecomputedValue.injected(
ModuleFileFunction.REGISTRIES, ImmutableList.of(registry.getUrl())))
.add(PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false))
.add(
PrecomputedValue.injected(
BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES,
CheckDirectDepsMode.WARNING))
.add(
PrecomputedValue.injected(
YankedVersionsUtil.ALLOWED_YANKED_VERSIONS, ImmutableList.of()))
.add(
PrecomputedValue.injected(
BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE,
BazelCompatibilityMode.ERROR))
.add(PrecomputedValue.injected(BazelLockFileFunction.LOCKFILE_MODE, LockfileMode.OFF))
.build());
SkyframeExecutorTestHelper.process(skyframeExecutor);
return skyframeExecutor;
}
protected abstract Iterable<EnvironmentExtension> getEnvironmentExtensions();
protected abstract BuildOptions getDefaultBuildOptions(
ConfiguredRuleClassProvider ruleClassProvider);
@Override
public void assertPackageNotLoaded(String packageName) throws Exception {
MemoizingEvaluator evaluator = skyframeExecutor.getEvaluator();
SkyKey key = PackageValue.key(PackageIdentifier.createInMainRepo(packageName));
if (evaluator.getExistingValue(key) != null
|| evaluator.getExistingErrorForTesting(key) != null) {
throw new IllegalStateException("Package was loaded: " + packageName);
}
}
@Override
public Path getModuleRoot() {
return moduleRoot;
}
/**
* A wrapper to maintain an ordered copy of set of targets which also respect equality rules
* defined by {@link ThreadSafeMutableSet}.
*/
private static class OrderedThreadSafeImmutableSet extends AbstractSet<Target> {
private final ThreadSafeMutableSet<Target> targetSet;
private final List<Target> orderedTargetList;
private OrderedThreadSafeImmutableSet(QueryEnvironment<Target> env, Set<Target> targets) {
this.targetSet = env.createThreadSafeMutableSet();
this.orderedTargetList = new ArrayList<>(targets.size());
// The order is determined by implementation of iterator on the source set of targets, which
// can be deterministic or non-deterministic.
for (Target target : targets) {
if (targetSet.add(target)) {
orderedTargetList.add(target);
}
}
}
@Override
public Iterator<Target> iterator() {
return orderedTargetList.iterator();
}
@Override
public int size() {
return targetSet.size();
}
@Override
public boolean add(Target element) {
throw new IllegalStateException("Add operation on immutable set is not supported.");
}
@Override
public boolean contains(Object obj) {
return targetSet.contains(obj);
}
@Override
public boolean remove(Object obj) {
throw new IllegalStateException("Remove operation on immutable set is not supported.");
}
}
}