blob: d15caa3b4729041f2ba0f763f5bbce8cbb72e26c [file] [log] [blame]
// Copyright 2021 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.bazel.bzlmod;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashFunction;
import com.google.devtools.build.lib.actions.FileStateValue;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.actions.ThreadStateReceiver;
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.util.AnalysisMock;
import com.google.devtools.build.lib.bazel.repository.starlark.StarlarkRepositoryModule;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.packages.PackageFactory;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
import com.google.devtools.build.lib.skyframe.BzlCompileFunction;
import com.google.devtools.build.lib.skyframe.BzlLoadFunction;
import com.google.devtools.build.lib.skyframe.BzlmodRepoRuleFunction;
import com.google.devtools.build.lib.skyframe.ContainingPackageLookupFunction;
import com.google.devtools.build.lib.skyframe.ExternalFilesHelper;
import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
import com.google.devtools.build.lib.skyframe.FileFunction;
import com.google.devtools.build.lib.skyframe.FileStateFunction;
import com.google.devtools.build.lib.skyframe.IgnoredPackagePrefixesFunction;
import com.google.devtools.build.lib.skyframe.LocalRepositoryLookupFunction;
import com.google.devtools.build.lib.skyframe.PackageFunction;
import com.google.devtools.build.lib.skyframe.PackageLookupFunction;
import com.google.devtools.build.lib.skyframe.PackageLookupFunction.CrossRepositoryLabelViolationStrategy;
import com.google.devtools.build.lib.skyframe.PrecomputedFunction;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.RepositoryMappingValue;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
import com.google.devtools.build.lib.starlarkbuildapi.repository.RepositoryBootstrap;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.UnixGlob;
import com.google.devtools.build.skyframe.EvaluationContext;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator;
import com.google.devtools.build.skyframe.MemoizingEvaluator;
import com.google.devtools.build.skyframe.RecordingDifferencer;
import com.google.devtools.build.skyframe.SequencedRecordingDifferencer;
import com.google.devtools.build.skyframe.SequentialBuildDriver;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.concurrent.atomic.AtomicReference;
import net.starlark.java.eval.StarlarkSemantics;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link BzlmodRepoRuleFunction}. */
@RunWith(JUnit4.class)
public final class BzlmodRepoRuleFunctionTest extends FoundationTestCase {
private SequentialBuildDriver driver;
private final RecordingDifferencer differencer = new SequencedRecordingDifferencer();
private EvaluationContext evaluationContext;
@Before
public void setup() throws Exception {
evaluationContext =
EvaluationContext.newBuilder().setNumThreads(8).setEventHandler(reporter).build();
AtomicReference<PathPackageLocator> packageLocator =
new AtomicReference<>(
new PathPackageLocator(
outputBase,
ImmutableList.of(Root.fromPath(rootDirectory)),
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
BlazeDirectories directories =
new BlazeDirectories(
new ServerDirectories(rootDirectory, outputBase, rootDirectory),
rootDirectory,
/* defaultSystemJavabase= */ null,
AnalysisMock.get().getProductName());
ExternalFilesHelper externalFilesHelper =
ExternalFilesHelper.createForTesting(
packageLocator,
ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
directories);
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
TestRuleClassProvider.addStandardRules(builder);
builder
.clearWorkspaceFileSuffixForTesting()
.addStarlarkBootstrap(new RepositoryBootstrap(new StarlarkRepositoryModule()));
ConfiguredRuleClassProvider ruleClassProvider = builder.build();
PackageFactory pkgFactory =
AnalysisMock.get()
.getPackageFactoryBuilderForTesting(directories)
.build(ruleClassProvider, fileSystem);
HashFunction hashFunction = fileSystem.getDigestFunction().getHashFunction();
MemoizingEvaluator evaluator =
new InMemoryMemoizingEvaluator(
ImmutableMap.<SkyFunctionName, SkyFunction>builder()
.put(FileValue.FILE, new FileFunction(packageLocator))
.put(
FileStateValue.FILE_STATE,
new FileStateFunction(
new AtomicReference<TimestampGranularityMonitor>(),
new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS),
externalFilesHelper))
.put(
BzlmodRepoRuleValue.BZLMOD_REPO_RULE,
new BzlmodRepoRuleFunction(
pkgFactory, ruleClassProvider, directories, getFakeBzlmodRepoRuleHelper()))
.put(
SkyFunctions.LOCAL_REPOSITORY_LOOKUP,
new LocalRepositoryLookupFunction(
BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER))
.put(
SkyFunctions.PACKAGE,
new PackageFunction(
/*packageFactory=*/ null,
/*pkgLocator=*/ null,
/*showLoadingProgress=*/ null,
/*packageFunctionCache=*/ null,
/*compiledBuildFileCache=*/ null,
/*numPackagesLoaded=*/ null,
/*bzlLoadFunctionForInlining=*/ null,
/*packageProgress=*/ null,
PackageFunction.ActionOnIOExceptionReadingBuildFile.UseOriginalIOException
.INSTANCE,
PackageFunction.IncrementalityIntent.INCREMENTAL,
ignored -> ThreadStateReceiver.NULL_INSTANCE))
.put(
SkyFunctions.PACKAGE_LOOKUP,
new PackageLookupFunction(
new AtomicReference<>(ImmutableSet.of()),
CrossRepositoryLabelViolationStrategy.ERROR,
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY,
BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER))
.put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction())
.put(SkyFunctions.BZL_COMPILE, new BzlCompileFunction(pkgFactory, hashFunction))
.put(
SkyFunctions.BZL_LOAD,
BzlLoadFunction.create(
pkgFactory, directories, hashFunction, Caffeine.newBuilder().build()))
.put(
SkyFunctions.BAZEL_MODULE_RESOLUTION,
new SkyFunction() {
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws SkyFunctionException, InterruptedException {
// Dummy function that returns a dep graph with just the root module in it.
return BazelModuleResolutionFunction.createValue(
ImmutableMap.of(ModuleKey.ROOT, Module.builder().build()),
ImmutableMap.of());
}
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
})
.put(
SkyFunctions.REPOSITORY_MAPPING,
new SkyFunction() {
@Override
public SkyValue compute(SkyKey skyKey, Environment env) {
// Dummy function that always falls back.
return RepositoryMappingValue.withMapping(
RepositoryMapping.ALWAYS_FALLBACK);
}
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
})
.put(
SkyFunctions.MODULE_EXTENSION_RESOLUTION,
new SkyFunction() {
@Override
public SkyValue compute(SkyKey skyKey, Environment env) {
// Dummy function that returns nothing.
return ModuleExtensionResolutionValue.create(
ImmutableMap.of(), ImmutableMap.of(), ImmutableListMultimap.of());
}
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
})
.put(SkyFunctions.CONTAINING_PACKAGE_LOOKUP, new ContainingPackageLookupFunction())
.put(
SkyFunctions.IGNORED_PACKAGE_PREFIXES,
new IgnoredPackagePrefixesFunction(
/*ignoredPackagePrefixesFile=*/ PathFragment.EMPTY_FRAGMENT))
.build(),
differencer);
driver = new SequentialBuildDriver(evaluator);
PrecomputedValue.STARLARK_SEMANTICS.set(differencer, StarlarkSemantics.DEFAULT);
PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, packageLocator.get());
setupRepoRules();
}
private void setupRepoRules() throws Exception {
scratch.file(rootDirectory.getRelative("tools/build_defs/repo/BUILD").getPathString());
scratch.file(
rootDirectory.getRelative("tools/build_defs/repo/http.bzl").getPathString(),
"def _http_archive_impl(ctx): pass",
"",
"http_archive = repository_rule(",
" implementation = _http_archive_impl,",
" attrs = {",
" \"url\": attr.string(),",
" \"sha256\": attr.string(),",
" })");
scratch.file(rootDirectory.getRelative("maven/BUILD").getPathString());
scratch.file(
rootDirectory.getRelative("maven/repo.bzl").getPathString(),
"def _maven_repo_impl(ctx): pass",
"",
"maven_repo = repository_rule(",
" implementation = _maven_repo_impl,",
" attrs = {",
" \"artifacts\": attr.string_list(),",
" \"repositories\": attr.string_list(),",
" })");
}
private FakeBzlmodRepoRuleHelper getFakeBzlmodRepoRuleHelper() {
ImmutableMap.Builder<String, RepoSpec> repoSpecs = ImmutableMap.builder();
repoSpecs
// repos from non-registry overrides
.put(
"A",
RepoSpec.builder()
.setRuleClassName("local_repository")
.setAttributes(
ImmutableMap.of(
"name", "A",
"path", "/foo/bar/A"))
.build())
// repos from Bazel modules
.put(
"B",
RepoSpec.builder()
.setBzlFile(
// In real world, this will be @bazel_tools//tools/build_defs/repo:http.bzl,
"//tools/build_defs/repo:http.bzl")
.setRuleClassName("http_archive")
.setAttributes(
ImmutableMap.of(
"name", "B",
"url", "https://foo/bar/B.zip",
"sha256", "1234abcd"))
.build())
// repos from module rules
.put(
"C",
RepoSpec.builder()
.setBzlFile("//maven:repo.bzl")
.setRuleClassName("maven_repo")
.setAttributes(
ImmutableMap.of(
"name", "C",
"artifacts",
ImmutableList.of("junit:junit:4.12", "com.google.guava:guava:19.0"),
"repositories",
ImmutableList.of(
"https://maven.google.com", "https://repo1.maven.org/maven2")))
.build());
return new FakeBzlmodRepoRuleHelper(repoSpecs.build());
}
@Test
public void createRepoRule_bazelTools() throws Exception {
EvaluationResult<BzlmodRepoRuleValue> result =
driver.evaluate(
ImmutableList.of(BzlmodRepoRuleValue.key("bazel_tools")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
BzlmodRepoRuleValue bzlmodRepoRuleValue = result.get(BzlmodRepoRuleValue.key("bazel_tools"));
Rule repoRule = bzlmodRepoRuleValue.getRule();
assertThat(repoRule.getRuleClass()).isEqualTo("local_repository");
assertThat(repoRule.getName()).isEqualTo("bazel_tools");
// In the test, the install base is set to rootDirectory, which is "/workspace".
assertThat(repoRule.getAttr("path", Type.STRING)).isEqualTo("/workspace/embedded_tools");
}
@Test
public void createRepoRule_localConfigPlatform() throws Exception {
// Skip this test in Blaze because local_config_platform is not available.
if (!AnalysisMock.get().isThisBazel()) {
return;
}
EvaluationResult<BzlmodRepoRuleValue> result =
driver.evaluate(
ImmutableList.of(BzlmodRepoRuleValue.key("local_config_platform")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
BzlmodRepoRuleValue bzlmodRepoRuleValue =
result.get(BzlmodRepoRuleValue.key("local_config_platform"));
Rule repoRule = bzlmodRepoRuleValue.getRule();
assertThat(repoRule.getRuleClass()).isEqualTo("local_config_platform");
assertThat(repoRule.getName()).isEqualTo("local_config_platform");
}
@Test
public void createRepoRule_overrides() throws Exception {
EvaluationResult<BzlmodRepoRuleValue> result =
driver.evaluate(ImmutableList.of(BzlmodRepoRuleValue.key("A")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
BzlmodRepoRuleValue bzlmodRepoRuleValue = result.get(BzlmodRepoRuleValue.key("A"));
Rule repoRule = bzlmodRepoRuleValue.getRule();
assertThat(repoRule.getRuleClassObject().isStarlark()).isFalse();
assertThat(repoRule.getRuleClass()).isEqualTo("local_repository");
assertThat(repoRule.getName()).isEqualTo("A");
assertThat(repoRule.getAttr("path", Type.STRING)).isEqualTo("/foo/bar/A");
}
@Test
public void createRepoRule_bazelModules() throws Exception {
// Using a starlark rule in a RepoSpec requires having run Selection first.
driver.evaluate(ImmutableList.of(BazelModuleResolutionValue.KEY), evaluationContext);
EvaluationResult<BzlmodRepoRuleValue> result =
driver.evaluate(ImmutableList.of(BzlmodRepoRuleValue.key("B")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
BzlmodRepoRuleValue bzlmodRepoRuleValue = result.get(BzlmodRepoRuleValue.key("B"));
Rule repoRule = bzlmodRepoRuleValue.getRule();
assertThat(repoRule.getRuleClassObject().isStarlark()).isTrue();
assertThat(repoRule.getRuleClass()).isEqualTo("http_archive");
assertThat(repoRule.getName()).isEqualTo("B");
assertThat(repoRule.getAttr("url", Type.STRING)).isEqualTo("https://foo/bar/B.zip");
assertThat(repoRule.getAttr("sha256", Type.STRING)).isEqualTo("1234abcd");
}
@Test
public void createRepoRule_moduleRules() throws Exception {
// Using a starlark rule in a RepoSpec requires having run Selection first.
driver.evaluate(ImmutableList.of(BazelModuleResolutionValue.KEY), evaluationContext);
EvaluationResult<BzlmodRepoRuleValue> result =
driver.evaluate(ImmutableList.of(BzlmodRepoRuleValue.key("C")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
BzlmodRepoRuleValue bzlmodRepoRuleValue = result.get(BzlmodRepoRuleValue.key("C"));
Rule repoRule = bzlmodRepoRuleValue.getRule();
assertThat(repoRule.getRuleClassObject().isStarlark()).isTrue();
assertThat(repoRule.getRuleClass()).isEqualTo("maven_repo");
assertThat(repoRule.getName()).isEqualTo("C");
assertThat(repoRule.getAttr("artifacts", Type.STRING_LIST))
.isEqualTo(ImmutableList.of("junit:junit:4.12", "com.google.guava:guava:19.0"));
assertThat(repoRule.getAttr("repositories", Type.STRING_LIST))
.isEqualTo(ImmutableList.of("https://maven.google.com", "https://repo1.maven.org/maven2"));
}
@Test
public void createRepoRule_notFound() throws Exception {
EvaluationResult<BzlmodRepoRuleValue> result =
driver.evaluate(ImmutableList.of(BzlmodRepoRuleValue.key("unknown")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
BzlmodRepoRuleValue bzlmodRepoRuleValue = result.get(BzlmodRepoRuleValue.key("unknown"));
assertThat(bzlmodRepoRuleValue).isEqualTo(BzlmodRepoRuleValue.REPO_RULE_NOT_FOUND_VALUE);
}
}