| // Copyright 2022 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.analysis; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.Action; |
| import com.google.devtools.build.lib.actions.CommandLineExpansionException; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryDirtinessChecker; |
| import com.google.devtools.build.lib.skyframe.SkyframeExecutorRepositoryHelpersHolder; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.Map.Entry; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.syntax.Location; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests that the repo mapping manifest file is properly generated for runfiles. */ |
| @RunWith(JUnit4.class) |
| public class RunfilesRepoMappingManifestTest extends BuildViewTestCase { |
| |
| @Override |
| protected SkyframeExecutorRepositoryHelpersHolder getRepositoryHelpersHolder() { |
| // Transitive packages are needed for RepoMappingManifestAction and are only stored when |
| // external repositories are enabled. |
| return SkyframeExecutorRepositoryHelpersHolder.create( |
| new RepositoryDirectoryDirtinessChecker()); |
| } |
| |
| /** |
| * Sets up a Bazel module bare_rule@1.0, which provides a bare_binary rule that passes along |
| * runfiles in the data attribute, and does nothing else. |
| */ |
| @Before |
| public void setupBareBinaryRule() throws Exception { |
| registry.addModule( |
| createModuleKey("bare_rule", "1.0"), "module(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile(moduleRoot.getRelative("bare_rule~1.0/WORKSPACE").getPathString()); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("bare_rule~1.0/defs.bzl").getPathString(), |
| "def _bare_binary_impl(ctx):", |
| " exe = ctx.actions.declare_file(ctx.label.name)", |
| " ctx.actions.write(exe, 'i got nothing', True)", |
| " runfiles = ctx.runfiles(files=ctx.files.data)", |
| " for data in ctx.attr.data:", |
| " runfiles = runfiles.merge(data[DefaultInfo].default_runfiles)", |
| " return DefaultInfo(files=depset(direct=[exe]), executable=exe, runfiles=runfiles)", |
| "bare_binary=rule(", |
| " implementation=_bare_binary_impl,", |
| " attrs={'data':attr.label_list(allow_files=True)},", |
| " executable=True,", |
| ")"); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("bare_rule~1.0/BUILD").getPathString(), |
| "load('//:defs.bzl', 'bare_binary')", |
| "bare_binary(name='bare_binary')"); |
| } |
| |
| private RepoMappingManifestAction getRepoMappingManifestActionForTarget(String label) |
| throws Exception { |
| Action action = getGeneratingAction(getRunfilesSupport(label).getRepoMappingManifest()); |
| assertThat(action).isInstanceOf(RepoMappingManifestAction.class); |
| return (RepoMappingManifestAction) action; |
| } |
| |
| private String computeKey(RepoMappingManifestAction action) |
| throws CommandLineExpansionException, EvalException, InterruptedException { |
| Fingerprint fp = new Fingerprint(); |
| action.computeKey(actionKeyContext, /* artifactExpander= */ null, fp); |
| return fp.hexDigestAndReset(); |
| } |
| |
| private ImmutableList<String> getRepoMappingManifestForTarget(String label) throws Exception { |
| return getRepoMappingManifestActionForTarget(label) |
| .newDeterministicWriter(null) |
| .getBytes() |
| .toStringUtf8() |
| .lines() |
| .collect(toImmutableList()); |
| } |
| |
| @Test |
| public void diamond() throws Exception { |
| scratch.overwriteFile( |
| "MODULE.bazel", |
| "module(name='aaa',version='1.0')", |
| "bazel_dep(name='bbb',version='1.0')", |
| "bazel_dep(name='ccc',version='2.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| registry.addModule( |
| createModuleKey("bbb", "1.0"), |
| "module(name='bbb',version='1.0')", |
| "bazel_dep(name='ddd',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| registry.addModule( |
| createModuleKey("ccc", "2.0"), |
| "module(name='ccc',version='2.0')", |
| "bazel_dep(name='ddd',version='2.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| registry.addModule( |
| createModuleKey("ddd", "1.0"), |
| "module(name='ddd',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| registry.addModule( |
| createModuleKey("ddd", "2.0"), |
| "module(name='ddd',version='2.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| |
| scratch.overwriteFile( |
| "BUILD", |
| "load('@bare_rule//:defs.bzl', 'bare_binary')", |
| "bare_binary(name='aaa',data=['@bbb'])"); |
| ImmutableMap<String, String> buildFiles = |
| ImmutableMap.of( |
| "bbb~1.0", "bare_binary(name='bbb',data=['@ddd'])", |
| "ccc~2.0", "bare_binary(name='ccc',data=['@ddd'])", |
| "ddd~1.0", "bare_binary(name='ddd')", |
| "ddd~2.0", "bare_binary(name='ddd')"); |
| for (Entry<String, String> entry : buildFiles.entrySet()) { |
| scratch.overwriteFile( |
| moduleRoot.getRelative(entry.getKey()).getRelative("WORKSPACE").getPathString()); |
| scratch.overwriteFile( |
| moduleRoot.getRelative(entry.getKey()).getRelative("BUILD").getPathString(), |
| "load('@bare_rule//:defs.bzl', 'bare_binary')", |
| entry.getValue()); |
| } |
| |
| // Called last as it triggers package invalidation, which requires a valid MODULE.bazel setup. |
| rewriteWorkspace("workspace(name='aaa_ws')"); |
| |
| assertThat(getRepoMappingManifestForTarget("//:aaa")) |
| .containsExactly( |
| ",aaa," + getRuleClassProvider().getRunfilesPrefix(), |
| ",aaa_ws," + getRuleClassProvider().getRunfilesPrefix(), |
| ",bbb,bbb~1.0", |
| "bbb~1.0,bbb,bbb~1.0", |
| "bbb~1.0,ddd,ddd~2.0", |
| "ddd~2.0,ddd,ddd~2.0") |
| .inOrder(); |
| assertThat(getRepoMappingManifestForTarget("@@ccc~2.0//:ccc")) |
| .containsExactly("ccc~2.0,ccc,ccc~2.0", "ccc~2.0,ddd,ddd~2.0", "ddd~2.0,ddd,ddd~2.0") |
| .inOrder(); |
| } |
| |
| @Test |
| public void runfilesFromToolchain() throws Exception { |
| scratch.overwriteFile("MODULE.bazel", "bazel_dep(name='tooled_rule',version='1.0')"); |
| // tooled_rule offers a tooled_binary rule, which uses a toolchain backed by a binary from |
| // bare_rule. tooled_binary explicitly requests that runfiles from this binary are included in |
| // its runfiles tree, which would mean that bare_rule should be included in the repo mapping |
| // manifest. |
| registry.addModule( |
| createModuleKey("tooled_rule", "1.0"), |
| "module(name='tooled_rule',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')", |
| "register_toolchains('//:all')"); |
| scratch.overwriteFile(moduleRoot.getRelative("tooled_rule~1.0/WORKSPACE").getPathString()); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("tooled_rule~1.0/defs.bzl").getPathString(), |
| "def _tooled_binary_impl(ctx):", |
| " exe = ctx.actions.declare_file(ctx.label.name)", |
| " ctx.actions.write(exe, 'i got something', True)", |
| " runfiles = ctx.runfiles(files=ctx.files.data)", |
| " for data in ctx.attr.data:", |
| " runfiles = runfiles.merge(data[DefaultInfo].default_runfiles)", |
| " runfiles = runfiles.merge(", |
| " ctx.toolchains['//:toolchain_type'].tooled_info[DefaultInfo].default_runfiles)", |
| " return DefaultInfo(files=depset(direct=[exe]), executable=exe, runfiles=runfiles)", |
| "tooled_binary = rule(", |
| " implementation=_tooled_binary_impl,", |
| " attrs={'data':attr.label_list(allow_files=True)},", |
| " executable=True,", |
| " toolchains=['//:toolchain_type'],", |
| ")", |
| "", |
| "def _tooled_toolchain_rule_impl(ctx):", |
| " return [platform_common.ToolchainInfo(tooled_info = ctx.attr.backing_binary)]", |
| "tooled_toolchain_rule=rule(_tooled_toolchain_rule_impl,", |
| " attrs={'backing_binary':attr.label()})", |
| "def tooled_toolchain(name, backing_binary):", |
| " tooled_toolchain_rule(name=name+'_impl',backing_binary=backing_binary)", |
| " native.toolchain(", |
| " name=name,", |
| " toolchain=':'+name+'_impl',", |
| " toolchain_type=Label('//:toolchain_type'),", |
| " )"); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("tooled_rule~1.0/BUILD").getPathString(), |
| "load('//:defs.bzl', 'tooled_toolchain')", |
| "toolchain_type(name='toolchain_type')", |
| "tooled_toolchain(name='tooled_toolchain', backing_binary='@bare_rule//:bare_binary')"); |
| |
| scratch.overwriteFile( |
| "BUILD", |
| "load('@tooled_rule//:defs.bzl', 'tooled_binary')", |
| "tooled_binary(name='tooled')"); |
| |
| // Called last as it triggers package invalidation, which requires a valid MODULE.bazel setup. |
| rewriteWorkspace("workspace(name='main')"); |
| |
| assertThat(getRepoMappingManifestForTarget("//:tooled")) |
| .containsExactly( |
| ",main," + getRuleClassProvider().getRunfilesPrefix(), |
| "bare_rule~1.0,bare_rule,bare_rule~1.0", |
| "tooled_rule~1.0,bare_rule,bare_rule~1.0") |
| .inOrder(); |
| } |
| |
| @Test |
| public void actionRerunsOnRepoMappingChange_workspaceName() throws Exception { |
| overwriteWorkspaceFile("workspace(name='aaa_ws')"); |
| scratch.overwriteFile( |
| "MODULE.bazel", |
| "module(name='aaa',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile( |
| "BUILD", "load('@bare_rule//:defs.bzl', 'bare_binary')", "bare_binary(name='aaa')"); |
| invalidatePackages(); |
| |
| RepoMappingManifestAction actionBeforeChange = getRepoMappingManifestActionForTarget("//:aaa"); |
| |
| overwriteWorkspaceFile("workspace(name='not_aaa_ws')"); |
| invalidatePackages(); |
| |
| RepoMappingManifestAction actionAfterChange = getRepoMappingManifestActionForTarget("//:aaa"); |
| assertThat(computeKey(actionBeforeChange)).isNotEqualTo(computeKey(actionAfterChange)); |
| } |
| |
| @Test |
| public void actionRerunsOnRepoMappingChange_repoName() throws Exception { |
| overwriteWorkspaceFile("workspace(name='aaa_ws')"); |
| scratch.overwriteFile( |
| "MODULE.bazel", |
| "module(name='aaa',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile( |
| "BUILD", "load('@bare_rule//:defs.bzl', 'bare_binary')", "bare_binary(name='aaa')"); |
| invalidatePackages(); |
| |
| RepoMappingManifestAction actionBeforeChange = getRepoMappingManifestActionForTarget("//:aaa"); |
| |
| scratch.overwriteFile( |
| "MODULE.bazel", |
| "module(name='aaa',version='1.0',repo_name='not_aaa')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| invalidatePackages(); |
| |
| RepoMappingManifestAction actionAfterChange = getRepoMappingManifestActionForTarget("//:aaa"); |
| assertThat(computeKey(actionBeforeChange)).isNotEqualTo(computeKey(actionAfterChange)); |
| } |
| |
| @Test |
| public void actionRerunsOnRepoMappingChange_newEntry() throws Exception { |
| overwriteWorkspaceFile("workspace(name='aaa_ws')"); |
| scratch.overwriteFile( |
| "MODULE.bazel", |
| "module(name='aaa',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile( |
| "BUILD", "load('@bare_rule//:defs.bzl', 'bare_binary')", "bare_binary(name='aaa')"); |
| |
| registry.addModule( |
| createModuleKey("bbb", "1.0"), |
| "module(name='bbb',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("bbb~1.0").getRelative("WORKSPACE").getPathString()); |
| scratch.overwriteFile(moduleRoot.getRelative("bbb~1.0").getRelative("BUILD").getPathString()); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("bbb~1.0").getRelative("def.bzl").getPathString(), "BBB = '1'"); |
| invalidatePackages(); |
| |
| RepoMappingManifestAction actionBeforeChange = getRepoMappingManifestActionForTarget("//:aaa"); |
| |
| scratch.overwriteFile( |
| "MODULE.bazel", |
| "module(name='aaa',version='1.0')", |
| "bazel_dep(name='bbb',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile( |
| "BUILD", |
| "load('@bare_rule//:defs.bzl', 'bare_binary')", |
| "load('@bbb//:def.bzl', 'BBB')", |
| "bare_binary(name='aaa')"); |
| invalidatePackages(); |
| |
| RepoMappingManifestAction actionAfterChange = getRepoMappingManifestActionForTarget("//:aaa"); |
| assertThat(computeKey(actionBeforeChange)).isNotEqualTo(computeKey(actionAfterChange)); |
| } |
| |
| @Test |
| public void hasMappingForSymlinks() throws Exception { |
| overwriteWorkspaceFile("workspace(name='my_workspace')"); |
| scratch.overwriteFile( |
| "MODULE.bazel", |
| "module(name='my_module',version='1.0')", |
| "bazel_dep(name='aaa',version='1.0')"); |
| |
| registry.addModule( |
| createModuleKey("aaa", "1.0"), |
| "module(name='aaa',version='1.0')", |
| "bazel_dep(name='my_module',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')", |
| "bazel_dep(name='symlinks',version='1.0')"); |
| scratch.overwriteFile(moduleRoot.getRelative("aaa~1.0/WORKSPACE").getPathString()); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("aaa~1.0/BUILD").getPathString(), |
| "load('@bare_rule//:defs.bzl', 'bare_binary')", |
| "bare_binary(name='aaa',data=['@symlinks'])"); |
| |
| registry.addModule( |
| createModuleKey("symlinks", "1.0"), |
| "module(name='symlinks',version='1.0')", |
| "bazel_dep(name='ddd',version='1.0')"); |
| scratch.overwriteFile(moduleRoot.getRelative("symlinks~1.0/WORKSPACE").getPathString()); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("symlinks~1.0/defs.bzl").getPathString(), |
| "def _symlinks_impl(ctx):", |
| " runfiles = ctx.runfiles(", |
| " symlinks = {'path/to/pkg/symlink': ctx.file.data},", |
| " root_symlinks = {ctx.label.workspace_name + '/path/to/pkg/root_symlink':" |
| + " ctx.file.data},", |
| " )", |
| " return DefaultInfo(runfiles=runfiles)", |
| "symlinks = rule(", |
| " implementation=_symlinks_impl,", |
| " attrs={'data':attr.label(allow_single_file=True)},", |
| ")"); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("symlinks~1.0/BUILD").getPathString(), |
| "load('//:defs.bzl', 'symlinks')", |
| "symlinks(name='symlinks',data='@ddd')"); |
| |
| registry.addModule( |
| createModuleKey("ddd", "1.0"), |
| "module(name='ddd',version='1.0')", |
| "bazel_dep(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile(moduleRoot.getRelative("ddd~1.0/WORKSPACE").getPathString()); |
| scratch.overwriteFile( |
| moduleRoot.getRelative("ddd~1.0/BUILD").getPathString(), |
| "load('@bare_rule//:defs.bzl', 'bare_binary')", |
| "bare_binary(name='ddd')"); |
| invalidatePackages(); |
| |
| RunfilesSupport runfilesSupport = getRunfilesSupport("@aaa~1.0//:aaa"); |
| ImmutableList<String> runfilesPaths = |
| runfilesSupport |
| .getRunfiles() |
| .getRunfilesInputs(reporter, Location.BUILTIN, runfilesSupport.getRepoMappingManifest()) |
| .keySet() |
| .stream() |
| .map(PathFragment::getPathString) |
| .collect(toImmutableList()); |
| assertThat(runfilesPaths) |
| .containsExactly( |
| "aaa~1.0/aaa", |
| getRuleClassProvider().getRunfilesPrefix() + "/external/aaa~1.0/aaa", |
| getRuleClassProvider().getRunfilesPrefix() + "/path/to/pkg/symlink", |
| "symlinks~1.0/path/to/pkg/root_symlink", |
| "_repo_mapping"); |
| |
| assertThat(getRepoMappingManifestForTarget("@aaa~1.0//:aaa")) |
| .containsExactly( |
| // @aaa~1.0 contributes the top-level executable to runfiles. |
| "aaa~1.0,aaa,aaa~1.0", |
| // The symlink is staged under the main repository's runfiles directory and aaa has a |
| // repo mapping entry for it. |
| "aaa~1.0,my_module," + getRuleClassProvider().getRunfilesPrefix(), |
| // @symlinks~1.0 appears as the first segment of a root symlink. |
| "aaa~1.0,symlinks,symlinks~1.0", |
| "symlinks~1.0,symlinks,symlinks~1.0") |
| .inOrder(); |
| } |
| |
| @Test |
| public void repoMappingOnFilesToRunProvider() throws Exception { |
| scratch.overwriteFile("MODULE.bazel", "bazel_dep(name='bare_rule',version='1.0')"); |
| scratch.overwriteFile( |
| "defs.bzl", |
| "def _get_repo_mapping_impl(ctx):", |
| " files_to_run = ctx.attr.bin[DefaultInfo].files_to_run", |
| " return [", |
| " DefaultInfo(files = depset([files_to_run.repo_mapping_manifest])),", |
| " ]", |
| "get_repo_mapping = rule(", |
| " implementation = _get_repo_mapping_impl,", |
| " attrs = {'bin':attr.label(cfg='target',executable=True)}", |
| ")"); |
| scratch.overwriteFile( |
| "BUILD", |
| "load('@bare_rule//:defs.bzl', 'bare_binary')", |
| "load('//:defs.bzl', 'get_repo_mapping')", |
| "bare_binary(name='aaa')", |
| "get_repo_mapping(name='get_repo_mapping', bin=':aaa')"); |
| invalidatePackages(); |
| |
| assertThat(getFilesToBuild(getConfiguredTarget("//:get_repo_mapping")).toList()) |
| .containsExactly(getRunfilesSupport("//:aaa").getRepoMappingManifest()); |
| } |
| |
| /** |
| * Similar to {@link BuildViewTestCase#rewriteWorkspace(String...)}, but does not call {@link |
| * BuildViewTestCase#invalidatePackages()}. |
| */ |
| public void overwriteWorkspaceFile(String... lines) throws Exception { |
| scratch.overwriteFile( |
| "WORKSPACE", |
| new ImmutableList.Builder<String>() |
| .addAll(analysisMock.getWorkspaceContents(mockToolsConfig)) |
| .addAll(ImmutableList.copyOf(lines)) |
| .build()); |
| } |
| } |