blob: d8fcf569b0be4877e0847036e04f1ac9ae7ff045 [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.Truth8.assertThat;
import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
import static org.junit.Assert.fail;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.FileStateValue;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
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.RepositoryOptions.CheckDirectDepsMode;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
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.PrecomputedFunction;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.UnixGlob;
import com.google.devtools.build.skyframe.AbstractSkyKey;
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.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
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 BzlmodRepoRuleHelperImpl}. */
@RunWith(JUnit4.class)
public final class BzlmodRepoRuleHelperTest extends FoundationTestCase {
private Path workspaceRoot;
private SequentialBuildDriver driver;
private RecordingDifferencer differencer;
private EvaluationContext evaluationContext;
private FakeRegistry.Factory registryFactory;
@Before
public void setup() throws Exception {
workspaceRoot = scratch.dir("/ws");
differencer = new SequencedRecordingDifferencer();
evaluationContext =
EvaluationContext.newBuilder().setNumThreads(8).setEventHandler(reporter).build();
registryFactory = new FakeRegistry.Factory();
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);
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(
SkyFunctions.MODULE_FILE,
new ModuleFileFunction(registryFactory, workspaceRoot))
.put(SkyFunctions.BAZEL_MODULE_RESOLUTION, new BazelModuleResolutionFunction())
.put(
GET_REPO_SPEC_BY_NAME_FUNCTION,
new GetRepoSpecByNameFunction(new BzlmodRepoRuleHelperImpl()))
.put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction())
.build(),
differencer);
driver = new SequentialBuildDriver(evaluator);
PrecomputedValue.STARLARK_SEMANTICS.set(differencer, StarlarkSemantics.DEFAULT);
ModuleFileFunction.IGNORE_DEV_DEPS.set(differencer, false);
BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES.set(
differencer, CheckDirectDepsMode.WARNING);
}
@Test
public void getRepoSpec_bazelModule() throws Exception {
scratch.file(
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
"module(name='A',version='0.1')",
"bazel_dep(name='B',version='1.0')");
FakeRegistry registry =
registryFactory
.newFakeRegistry("/usr/local/modules")
.addModule(
createModuleKey("B", "1.0"),
"module(name='B', version='1.0');bazel_dep(name='C',version='2.0')")
.addModule(createModuleKey("C", "2.0"), "module(name='C', version='2.0')");
ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
EvaluationResult<GetRepoSpecByNameValue> result =
driver.evaluate(ImmutableList.of(getRepoSpecByNameKey("C.2.0")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
Optional<RepoSpec> repoSpec = result.get(getRepoSpecByNameKey("C.2.0")).rule();
assertThat(repoSpec)
.hasValue(
RepoSpec.builder()
.setRuleClassName("local_repository")
.setAttributes(ImmutableMap.of("name", "C.2.0", "path", "/usr/local/modules/C.2.0"))
.build());
}
@Test
public void getRepoSpec_nonRegistryOverride() throws Exception {
scratch.file(
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
"module(name='A',version='0.1')",
"bazel_dep(name='B',version='1.0')",
"local_path_override(module_name='C',path='/foo/bar/C')");
FakeRegistry registry =
registryFactory
.newFakeRegistry("/usr/local/modules")
.addModule(
createModuleKey("B", "1.0"),
"module(name='B', version='1.0');bazel_dep(name='C',version='2.0')")
.addModule(createModuleKey("C", "2.0"), "module(name='C', version='2.0')");
ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
EvaluationResult<GetRepoSpecByNameValue> result =
driver.evaluate(ImmutableList.of(getRepoSpecByNameKey("C")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
Optional<RepoSpec> repoSpec = result.get(getRepoSpecByNameKey("C")).rule();
assertThat(repoSpec)
.hasValue(
RepoSpec.builder()
.setRuleClassName("local_repository")
.setAttributes(
ImmutableMap.of(
"name", "C",
"path", "/foo/bar/C"))
.build());
}
@Test
public void getRepoSpec_singleVersionOverride() throws Exception {
scratch.file(
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
"module(name='A',version='0.1')",
"bazel_dep(name='B',version='1.0')",
"single_version_override(",
" module_name='C',version='3.0',patches=['//:foo.patch'],patch_strip=1)");
FakeRegistry registry =
registryFactory
.newFakeRegistry("/usr/local/modules")
.addModule(
createModuleKey("B", "1.0"),
"module(name='B', version='1.0');bazel_dep(name='C',version='2.0')")
.addModule(createModuleKey("C", "2.0"), "module(name='C', version='2.0')")
.addModule(createModuleKey("C", "3.0"), "module(name='C', version='3.0')");
ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
EvaluationResult<GetRepoSpecByNameValue> result =
driver.evaluate(ImmutableList.of(getRepoSpecByNameKey("C.3.0")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
Optional<RepoSpec> repoSpec = result.get(getRepoSpecByNameKey("C.3.0")).rule();
assertThat(repoSpec)
.hasValue(
RepoSpec.builder()
// This obviously wouldn't work in the real world since local_repository doesn't
// support patches -- but in the real world, registries also don't use
// local_repository.
.setRuleClassName("local_repository")
.setAttributes(
ImmutableMap.of(
"name",
"C.3.0",
"path",
"/usr/local/modules/C.3.0",
"patches",
ImmutableList.of("//:foo.patch"),
"patch_args",
ImmutableList.of("-p1")))
.build());
}
@Test
public void getRepoSpec_multipleVersionOverride() throws Exception {
scratch.file(
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
"module(name='A',version='0.1')",
"bazel_dep(name='B',version='1.0')",
"bazel_dep(name='C',version='2.0')",
"multiple_version_override(module_name='D',versions=['1.0','2.0'])");
FakeRegistry registry =
registryFactory
.newFakeRegistry("/usr/local/modules")
.addModule(
createModuleKey("B", "1.0"),
"module(name='B', version='1.0');bazel_dep(name='D',version='1.0')")
.addModule(
createModuleKey("C", "2.0"),
"module(name='C', version='2.0');bazel_dep(name='D',version='2.0')")
.addModule(createModuleKey("D", "1.0"), "module(name='D', version='1.0')")
.addModule(createModuleKey("D", "2.0"), "module(name='D', version='2.0')");
ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
EvaluationResult<GetRepoSpecByNameValue> result =
driver.evaluate(ImmutableList.of(getRepoSpecByNameKey("D.2.0")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
Optional<RepoSpec> repoSpec = result.get(getRepoSpecByNameKey("D.2.0")).rule();
assertThat(repoSpec)
.hasValue(
RepoSpec.builder()
.setRuleClassName("local_repository")
.setAttributes(ImmutableMap.of("name", "D.2.0", "path", "/usr/local/modules/D.2.0"))
.build());
}
@Test
public void getRepoSpec_notFound() throws Exception {
scratch.file(
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
"module(name='A',version='0.1')",
"bazel_dep(name='B',version='1.0')");
FakeRegistry registry =
registryFactory
.newFakeRegistry("/usr/local/modules")
.addModule(createModuleKey("B", "1.0"), "module(name='B', version='1.0')");
ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
EvaluationResult<GetRepoSpecByNameValue> result =
driver.evaluate(ImmutableList.of(getRepoSpecByNameKey("C")), evaluationContext);
if (result.hasError()) {
fail(result.getError().toString());
}
Optional<RepoSpec> repoSpec = result.get(getRepoSpecByNameKey("C")).rule();
assertThat(repoSpec).isEmpty();
}
/** A helper SkyFunction to invoke BzlmodRepoRuleHelper */
private static final SkyFunctionName GET_REPO_SPEC_BY_NAME_FUNCTION =
SkyFunctionName.createHermetic("GET_REPO_SPEC_BY_NAME_FUNCTION");
@AutoValue
abstract static class GetRepoSpecByNameValue implements SkyValue {
abstract Optional<RepoSpec> rule();
static GetRepoSpecByNameValue create(Optional<RepoSpec> rule) {
return new AutoValue_BzlmodRepoRuleHelperTest_GetRepoSpecByNameValue(rule);
}
}
private static final class GetRepoSpecByNameFunction implements SkyFunction {
private final BzlmodRepoRuleHelper bzlmodRepoRuleHelper;
public GetRepoSpecByNameFunction(BzlmodRepoRuleHelper bzlmodRepoRuleHelper) {
this.bzlmodRepoRuleHelper = bzlmodRepoRuleHelper;
}
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws SkyFunctionException, InterruptedException {
String repositoryName = (String) skyKey.argument();
Optional<RepoSpec> result;
try {
result = bzlmodRepoRuleHelper.getRepoSpec(env, repositoryName);
if (env.valuesMissing()) {
return null;
}
} catch (IOException e) {
throw new GetRepoSpecByNameFunctionException(e, Transience.PERSISTENT);
}
return GetRepoSpecByNameValue.create(result);
}
@Nullable
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
}
private static final class Key extends AbstractSkyKey<String> {
private Key(String arg) {
super(arg);
}
@Override
public SkyFunctionName functionName() {
return GET_REPO_SPEC_BY_NAME_FUNCTION;
}
}
private static final class GetRepoSpecByNameFunctionException extends SkyFunctionException {
GetRepoSpecByNameFunctionException(IOException e, Transience transience) {
super(e, transience);
}
}
private static SkyKey getRepoSpecByNameKey(String repositoryName) {
return new Key(repositoryName);
}
}