diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java index f62086db419914..492ef67bef1eea 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java @@ -375,7 +375,18 @@ private RepositoryDirectoryValue.Builder fetchInternal( new IOException(rule + " must create a directory"), Transience.TRANSIENT); } + // Make sure the fetched repo has a boundary file. if (!WorkspaceFileHelper.isValidRepoRoot(outputDirectory)) { + if (outputDirectory.isSymbolicLink()) { + // The created repo is actually just a symlink to somewhere else (think local_repository). + // In this case, we shouldn't try to create the repo boundary file ourselves, but report an + // error instead. + throw new RepositoryFunctionException( + new IOException( + "No MODULE.bazel, REPO.bazel, or WORKSPACE file found in " + outputDirectory), + Transience.TRANSIENT); + } + // Otherwise, we can just create an empty REPO.bazel file. try { FileSystemUtils.createEmptyFile(outputDirectory.getRelative(LabelConstants.REPO_FILE_NAME)); if (starlarkSemantics.getBool(BuildLanguageOptions.ENABLE_WORKSPACE)) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryFunction.java index 2b5482ea82e3f9..931500575ddde9 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryFunction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryFunction.java @@ -46,6 +46,8 @@ public RepositoryDirectoryValue.Builder fetch( Map recordedInputValues, SkyKey key) throws InterruptedException, RepositoryFunctionException { + // DO NOT MODIFY THIS! It's being deprecated in favor of Starlark counterparts. + // See https://github.com/bazelbuild/bazel/issues/18285 String userDefinedPath = RepositoryFunction.getPathAttr(rule); Path targetPath = directories.getWorkspace().getRelative(userDefinedPath); RepositoryDirectoryValue.Builder result = diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryRule.java b/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryRule.java index 44c3351f78d376..83b1f136fbdc94 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryRule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/repository/LocalRepositoryRule.java @@ -31,6 +31,8 @@ public class LocalRepositoryRule implements RuleDefinition { @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + // DO NOT MODIFY THIS! It's being deprecated in favor of Starlark counterparts. + // See https://github.com/bazelbuild/bazel/issues/18285 return builder /* The path to the local repository's directory. diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryFunction.java index 7bc6c444cebd88..009bcd6f40b259 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryFunction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryFunction.java @@ -62,6 +62,8 @@ public RepositoryDirectoryValue.Builder fetch( Map recordedInputValues, SkyKey key) throws InterruptedException, RepositoryFunctionException { + // DO NOT MODIFY THIS! It's being deprecated in favor of Starlark counterparts. + // See https://github.com/bazelbuild/bazel/issues/18285 NewRepositoryFileHandler fileHandler = new NewRepositoryFileHandler(directories.getWorkspace()); if (!fileHandler.prepareFile(rule, env)) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryRule.java b/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryRule.java index c747bf37e38bcd..6bc47c55998e48 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryRule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/repository/NewLocalRepositoryRule.java @@ -31,6 +31,8 @@ public class NewLocalRepositoryRule implements RuleDefinition { @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + // DO NOT MODIFY THIS! It's being deprecated in favor of Starlark counterparts. + // See https://github.com/bazelbuild/bazel/issues/18285 return builder /* A path on the local filesystem. diff --git a/src/test/py/bazel/bazel_external_repository_test.py b/src/test/py/bazel/bazel_external_repository_test.py index 69ba318f0535ed..b3aef1a8d849f9 100644 --- a/src/test/py/bazel/bazel_external_repository_test.py +++ b/src/test/py/bazel/bazel_external_repository_test.py @@ -241,6 +241,10 @@ def testNewLocalRepositoryNoticesFileChangeInRepoRoot(self): """Regression test for https://github.com/bazelbuild/bazel/issues/7063.""" rule_definition = [ 'load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")', + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "new_local_repository")' + ), 'new_local_repository(', ' name = "r",', ' path = "./repo",', @@ -280,9 +284,16 @@ def testDeletedPackagesOnExternalRepo(self): ]) self.ScratchFile('other_repo/pkg/ignore/file') work_dir = self.ScratchDir('my_repo') - self.ScratchFile('my_repo/WORKSPACE', [ - "local_repository(name = 'other_repo', path='../other_repo')", - ]) + self.ScratchFile( + 'my_repo/WORKSPACE', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + "local_repository(name = 'other_repo', path='../other_repo')", + ], + ) exit_code, _, stderr = self.RunBazel( args=['build', '@other_repo//pkg:file'], @@ -317,9 +328,16 @@ def testBazelignoreFileOnExternalRepo(self): ]) self.ScratchFile('other_repo/pkg/ignore/file.txt') work_dir = self.ScratchDir('my_repo') - self.ScratchFile('my_repo/WORKSPACE', [ - 'local_repository(name = "other_repo", path="../other_repo")', - ]) + self.ScratchFile( + 'my_repo/WORKSPACE', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + 'local_repository(name = "other_repo", path="../other_repo")', + ], + ) exit_code, _, stderr = self.RunBazel( args=['build', '@other_repo//pkg:file'], @@ -368,9 +386,16 @@ def testUniverseScopeWithBazelIgnoreInExternalRepo(self): ]) work_dir = self.ScratchDir('my_repo') - self.ScratchFile('my_repo/WORKSPACE', [ - 'local_repository(name = "other_repo", path="../other_repo")', - ]) + self.ScratchFile( + 'my_repo/WORKSPACE', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + 'local_repository(name = "other_repo", path="../other_repo")', + ], + ) _, stdout, _ = self.RunBazel( args=[ @@ -395,9 +420,16 @@ def testBazelignoreFileFromMainRepoDoesNotAffectExternalRepos(self): self.ScratchFile('other_repo/foo/bar/file.txt') work_dir = self.ScratchDir('my_repo') - self.ScratchFile('my_repo/WORKSPACE', [ - 'local_repository(name = "other_repo", path="../other_repo")', - ]) + self.ScratchFile( + 'my_repo/WORKSPACE', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + 'local_repository(name = "other_repo", path="../other_repo")', + ], + ) # This should not exclude @other_repo//foo/bar self.ScratchFile('my_repo/.bazelignore', ['foo/bar']) @@ -420,9 +452,16 @@ def testBazelignoreFileFromExternalRepoDoesNotAffectMainRepo(self): ')', ]) self.ScratchFile('my_repo/foo/bar/file.txt') - self.ScratchFile('my_repo/WORKSPACE', [ - 'local_repository(name = "other_repo", path="../other_repo")', - ]) + self.ScratchFile( + 'my_repo/WORKSPACE', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + 'local_repository(name = "other_repo", path="../other_repo")', + ], + ) _, stdout, _ = self.RunBazel(args=['query', '//foo/bar/...'], cwd=work_dir) self.assertIn('//foo/bar:file', ''.join(stdout)) @@ -438,9 +477,16 @@ def testMainBazelignoreContainingRepoName(self): self.ScratchFile('other_repo/foo/bar/file.txt') work_dir = self.ScratchDir('my_repo') - self.ScratchFile('my_repo/WORKSPACE', [ - 'local_repository(name = "other_repo", path="../other_repo")', - ]) + self.ScratchFile( + 'my_repo/WORKSPACE', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + 'local_repository(name = "other_repo", path="../other_repo")', + ], + ) # This should not exclude @other_repo//foo/bar, because .bazelignore doesn't # support having repository name in the path fragment. self.ScratchFile('my_repo/.bazelignore', ['@other_repo//foo/bar']) @@ -470,10 +516,17 @@ def testExternalBazelignoreContainingRepoName(self): self.ScratchFile('third_repo/foo/bar/file.txt') work_dir = self.ScratchDir('my_repo') - self.ScratchFile('my_repo/WORKSPACE', [ - 'local_repository(name = "other_repo", path="../other_repo")', - 'local_repository(name = "third_repo", path="../third_repo")', - ]) + self.ScratchFile( + 'my_repo/WORKSPACE', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + 'local_repository(name = "other_repo", path="../other_repo")', + 'local_repository(name = "third_repo", path="../third_repo")', + ], + ) self.RunBazel(args=['build', '@other_repo//:file'], cwd=work_dir) diff --git a/src/test/py/bazel/bazel_workspace_test.py b/src/test/py/bazel/bazel_workspace_test.py index ccd487b0fd8d39..c414f2075f715f 100644 --- a/src/test/py/bazel/bazel_workspace_test.py +++ b/src/test/py/bazel/bazel_workspace_test.py @@ -58,7 +58,15 @@ def testWorkspaceDotBazelFileWithExternalRepo(self): # Test WORKSPACE.bazel takes priority over WORKSPACE self.ScratchFile("B/WORKSPACE") workspace_dot_bazel = self.ScratchFile( - "B/WORKSPACE.bazel", ["local_repository(name = 'A', path='../A')"]) + "B/WORKSPACE.bazel", + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + "local_repository(name = 'A', path='../A')", + ], + ) self.ScratchFile("B/bin.py") self.ScratchFile("B/BUILD", [ "py_binary(", @@ -78,8 +86,16 @@ def testWorkspaceDotBazelFileWithExternalRepo(self): self.assertIn("no such package '@@A//'", "".join(stderr)) # Test a WORKSPACE.bazel directory won't confuse Bazel - self.ScratchFile("B/WORKSPACE", - ["local_repository(name = 'A', path='../A')"]) + self.ScratchFile( + "B/WORKSPACE", + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + "local_repository(name = 'A', path='../A')", + ], + ) self.ScratchDir("B/WORKSPACE.bazel") self.RunBazel(args=["build", ":bin"], cwd=work_dir) diff --git a/src/test/py/bazel/bzlmod/bazel_module_test.py b/src/test/py/bazel/bzlmod/bazel_module_test.py index 81d15996b2f6dd..6286e05f50f827 100644 --- a/src/test/py/bazel/bzlmod/bazel_module_test.py +++ b/src/test/py/bazel/bzlmod/bazel_module_test.py @@ -436,6 +436,10 @@ def testWorkspaceEvaluatedBzlCanSeeRootModuleMappings(self): self.ScratchFile( 'WORKSPACE.bzlmod', [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), 'local_repository(name="foo", path="foo", repo_mapping={', ' "@bar":"@baz",', ' "@my_aaa":"@aaa",', @@ -497,6 +501,10 @@ def testNoModuleDotBazelAndFallbackToWorkspace(self): self.ScratchFile( 'WORKSPACE', [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), 'local_repository(name="hello", path="hello")', 'load("@hello//:world.bzl", "message")', 'print(message)', @@ -562,7 +570,14 @@ def testNativeModuleNameAndVersion(self): ], ) self.ScratchFile( - 'WORKSPACE.bzlmod', ['local_repository(name="quux",path="quux")'] + 'WORKSPACE.bzlmod', + [ + ( + 'load("@bazel_tools//tools/build_defs/repo:local.bzl",' + ' "local_repository")' + ), + 'local_repository(name="quux",path="quux")', + ], ) self.ScratchFile( 'BUILD', diff --git a/src/test/shell/bazel/check_external_files_test.sh b/src/test/shell/bazel/check_external_files_test.sh index 0751c0527e131f..ae78fd7b8e8e0b 100755 --- a/src/test/shell/bazel/check_external_files_test.sh +++ b/src/test/shell/bazel/check_external_files_test.sh @@ -104,6 +104,7 @@ setup_local() { mkdir main cd main cat >> "$(create_workspace_with_default_repos WORKSPACE)" <& "$TEST_log" && fail "Expected build to fail" || true - expect_log "no such package '@@bazel_tools//tools/build_defs/repo'" + expect_log "no such package" + expect_log "fetching repositories is disabled" bazel build \ --fetch \ --noexperimental_check_external_repository_files \ diff --git a/src/test/shell/bazel/external_integration_test.sh b/src/test/shell/bazel/external_integration_test.sh index ca6c8fc8ae139e..d416f0cee1f916 100755 --- a/src/test/shell/bazel/external_integration_test.sh +++ b/src/test/shell/bazel/external_integration_test.sh @@ -1113,6 +1113,7 @@ EOF function test_use_bind_as_repository() { cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF' +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") local_repository(name = 'foobar', path = 'foo') bind(name = 'foo', actual = '@foobar//:test') EOF @@ -1163,6 +1164,7 @@ function test_flip_flopping() { cd - cat > local_ws <> $(create_workspace_with_default_repos WORKSPACE) < ${WORKSPACE_DIR}/WORKSPACE < WORKSPACE < WORKSPACE <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <& $TEST_log || fail "query failed" expect_log "//external:my_repo" } function test_repository_package_query() { mkdir a b b/b - echo "local_repository(name='b', path='b')" > WORKSPACE + cat > WORKSPACE < a/BUILD create_workspace_with_default_repos b/WORKSPACE echo "sh_library(name='b')" > b/b/BUILD @@ -426,7 +437,10 @@ function test_repository_package_query() { function test_repository_buildfiles_package_query() { mkdir a b b/b b/c - echo "local_repository(name='b', path='b')" > WORKSPACE + cat > WORKSPACE < a/BUILD touch b/WORKSPACE b/c/BUILD cat > b/b/BUILD < "$bar/WORKSPACE" <> $(create_workspace_with_default_repos WORKSPACE) <WORKSPACE <WORKSPACE <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) < green/BUILD <> $(create_workspace_with_default_repos WORKSPACE) < green/BUILD < WORKSPACE <<'eof' -workspace(name='myws') -# add a `load` to force a new workspace chunk, adding "myws" to the mapping -load('@bazel_tools//tools/build_defs/repo:http.bzl', 'http_archive') -new_local_repository( - name = "heh", - path = "subdir", - build_file = "@myws//:thing", -) -eof - touch BUILD - echo 'filegroup(name="a-ma-bob")' > thing - write_default_lockfile MODULE.bazel.lock - - bazel build @heh//:a-ma-bob &> $TEST_log || fail "don't fail!" -} - # Regression test for GitHub issue #6351, see # https://github.com/bazelbuild/bazel/issues/6351#issuecomment-465488344 function test_glob_in_synthesized_build_file() { @@ -90,6 +71,7 @@ function test_glob_in_synthesized_build_file() { mkdir $pkg/B || fail "mkdir $pkg/B" cat >$pkg/A/WORKSPACE <<'eof' +load("@bazel_tools//tools/build_defs/repo:local.bzl", "new_local_repository") new_local_repository( name = "B", build_file_content = """ @@ -143,6 +125,7 @@ function test_recursive_glob_in_new_local_repository() { touch "$pkg/B/subdir/outer.txt" touch "$pkg/B/subdir/inner/inner.txt" cat >"$pkg/A/WORKSPACE" <test.bzl < zoo/BUILD <<'EOF' genrule( - name = "ball-pit1", + name = "ball-pit", srcs = ["@endangered//carnivore:mongoose"], - outs = ["ball-pit1.txt"], - cmd = "cat $< >$@", -) - -genrule( - name = "ball-pit2", - srcs = ["//external:mongoose"], - outs = ["ball-pit2.txt"], + outs = ["ball-pit.txt"], cmd = "cat $< >$@", ) EOF - bazel build //zoo:ball-pit1 >& $TEST_log || fail "Failed to build" + bazel build //zoo:ball-pit >& $TEST_log || fail "Failed to build" expect_log "bleh" expect_log "Tra-la!" # Invalidation - cat bazel-bin/zoo/ball-pit1.txt >$TEST_log + cat bazel-bin/zoo/ball-pit.txt >$TEST_log expect_log "Tra-la!" - bazel build //zoo:ball-pit1 >& $TEST_log || fail "Failed to build" + bazel build //zoo:ball-pit >& $TEST_log || fail "Failed to build" expect_not_log "Tra-la!" # No invalidation - bazel build //zoo:ball-pit2 >& $TEST_log || fail "Failed to build" - expect_not_log "Tra-la!" # No invalidation - cat bazel-bin/zoo/ball-pit2.txt >$TEST_log - expect_log "Tra-la!" - # Test invalidation of the WORKSPACE file create_new_workspace repo2=$new_workspace_dir @@ -151,24 +137,19 @@ genrule( EOF cd ${WORKSPACE_DIR} cat >test.bzl <& $TEST_log || fail "Failed to build" + bazel build //zoo:ball-pit >& $TEST_log || fail "Failed to build" expect_log "blah" expect_log "Tra-la-la!" # Invalidation - cat bazel-bin/zoo/ball-pit1.txt >$TEST_log + cat bazel-bin/zoo/ball-pit.txt >$TEST_log expect_log "Tra-la-la!" - bazel build //zoo:ball-pit1 >& $TEST_log || fail "Failed to build" + bazel build //zoo:ball-pit >& $TEST_log || fail "Failed to build" expect_not_log "Tra-la-la!" # No invalidation - - bazel build //zoo:ball-pit2 >& $TEST_log || fail "Failed to build" - expect_not_log "Tra-la-la!" # No invalidation - cat bazel-bin/zoo/ball-pit2.txt >$TEST_log - expect_log "Tra-la-la!" } function test_load_from_symlink_to_outside_of_workspace() { @@ -211,9 +192,10 @@ EOF # Our macro cat >WORKSPACE cat >test.bzl <BUILD <<'EOF' exports_files(["test.bzl"]) @@ -221,6 +203,7 @@ EOF cd ${WORKSPACE_DIR} cat >> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) <& $TEST_log || fail "Failed to build" expect_log "foo" - expect_not_log "Workspace name in .*/WORKSPACE (.*) does not match the name given in the repository's definition (@foo)" cat bazel-bin/external/foo/bar.txt >$TEST_log expect_log "foo" } @@ -1389,9 +1373,10 @@ EOF # Our macro cat >test.bzl < BUILD cat >> WORKSPACE < WORKSPACE < WORKSPACE < WORKSPACE <> $(create_workspace_with_default_repos WORKSPACE) <> $(create_workspace_with_default_repos WORKSPACE) < foo.bzl < foo/WORKSPACE <> $(create_workspace_with_default_repos WORKSPACE) < tree/WORKSPACE < WORKSPACE < main/WORKSPACE < main/WORKSPACE < main/WORKSPACE < main/WORKSPACE <main/WORKSPACE <main/WORKSPACE < mainrepo/WORKSPACE< mainrepo/WORKSPACE< WORKSPACE - bazel build //external:local --build_event_json_file=bep.json \ - > "${TEST_log}" 2>&1 \ - || fail "Accessing a repo through the //extern package should not fail" + cat > WORKSPACE < "${TEST_log}" 2>&1 \ + && fail "building a thing under //external shouldn't work" expect_not_log 'IllegalArgumentException' - grep '"id".*"targetCompleted".*"label".*"//external:local"' bep.json > completion.json \ - || fail "expected completion of //external:local being reported" - grep '"success".*true' completion.json \ - || fail "Success of //external:local expected" + expect_log "Found reference to a workspace rule in a context where a build rule was expected" } function test_external_rule() { @@ -883,6 +900,7 @@ http_archive( strip_prefix="true", ) +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") local_repository( name="extref", path="../extref", @@ -1065,6 +1083,7 @@ EOF mkdir mainrepo cd mainrepo cat > WORKSPACE <<'EOF' +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") local_repository( name = "source", path = "../local_a", @@ -1080,6 +1099,7 @@ EOF # Now, verify the same with for renamed to bar. cat > WORKSPACE <<'EOF' +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") local_repository( name = "source", path = "../local_a", @@ -1096,6 +1116,7 @@ EOF # Finally, verify the same with a renaming in the other repository cat > WORKSPACE <<'EOF' +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") local_repository( name = "origin", path = "../local_a", @@ -1126,6 +1147,7 @@ EOF cd mainrepo cat > WORKSPACE <<'EOF' workspace(name="foo") +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") local_repository( name = "source", path = "../local_a", @@ -1159,6 +1181,7 @@ EOF cd mainrepo cat > WORKSPACE <<'EOF' workspace(name="foo") +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") local_repository( name = "source", path = "../local_a", @@ -1200,23 +1223,4 @@ EOF bazel build @foo//:it || fail "Expected success" } -function test_cannot_define_repo_named_builtins() { - # The name "@_builtins" is reserved for use by builtins injection. - # It should be disallowed as a user repo name anyway because it doesn't - # begin with a letter. - cat > WORKSPACE <<'EOF' -local_repository( - name = "_builtins", - path = "subrepo", -) -EOF - mkdir -p subrepo - touch subrepo/WORKSPACE - touch BUILD - - bazel build //:BUILD \ - && fail "Expected to be unable to define a repo named @_builtins" \ - || true -} - run_suite "workspace tests" diff --git a/tools/build_defs/repo/BUILD b/tools/build_defs/repo/BUILD index b545c12ad475bd..5106cd4aa4a06e 100644 --- a/tools/build_defs/repo/BUILD +++ b/tools/build_defs/repo/BUILD @@ -42,6 +42,7 @@ genrule( REPO_BZL_FILES = [ "git", "http", + "local", "utils", ] diff --git a/tools/build_defs/repo/index.md b/tools/build_defs/repo/index.md index 80a428ab03090d..9d0041829e53a2 100644 --- a/tools/build_defs/repo/index.md +++ b/tools/build_defs/repo/index.md @@ -5,6 +5,7 @@ Book: /_book.yaml * [Rules related to git](git) * [Rules related to http](http) +* [Rules related to local directories](local) # Generic functions for repository rule authors diff --git a/tools/build_defs/repo/local.bzl b/tools/build_defs/repo/local.bzl new file mode 100644 index 00000000000000..69e0c1ae0b98a5 --- /dev/null +++ b/tools/build_defs/repo/local.bzl @@ -0,0 +1,133 @@ +# Copyright 2024 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. + +# WARNING: +# https://github.com/bazelbuild/bazel/issues/17713 +# .bzl files in this package (tools/build_defs/repo) are evaluated +# in a Starlark environment without "@_builtins" injection, and must not refer +# to symbols associated with build/workspace .bzl files + +"""Rules for making directories in the local filesystem available as repos. + +### Setup + +To use these rules in a module extension, load them in your .bzl file and then call them from your +extension's implementation function. For example, to use `local_repository`: + +```python +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") + +def _my_extension_impl(mctx): + local_repository(name = "foo", path = "foo") + +my_extension = module_extension(implementation = _my_extension_impl) +``` + +Alternatively, you can directly call these repo rules in your MODULE.bazel file with +`use_repo_rule`: + +```python +local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") +local_repository(name = "foo", path = "foo") +``` +""" + +# The default value for the string attr `build_file_content`. String attrs default to the empty +# string by default, but we need another default because the empty string is perfectly valid build +# file content and we need to know whether the attribute is actually set. +_UNSET = "_UNSET" + +def _get_dir_path(rctx): + """Turns the string attr `path` into a path object, ensuring that it's a directory.""" + path = rctx.workspace_root.get_child(rctx.attr.path) + if not path.is_dir: + fail( + ("The repository's path is \"%s\" (absolute: \"%s\") but it does not exist or is not " + + "a directory.") % (rctx.attr.path, path), + ) + return path + +def _local_repository_impl(rctx): + rctx.symlink(_get_dir_path(rctx), ".") + +local_repository = repository_rule( + implementation = _local_repository_impl, + attrs = { + "path": attr.string( + doc = + "The path to the directory to make available as a repo.

The path can be " + + "either absolute, or relative to the workspace root.", + mandatory = True, + ), + }, + doc = + "Makes a local directory that already contains Bazel files available as a repo. This " + + "directory should contain Bazel BUILD files and a repo boundary file already. If it " + + "doesn't contain these files, consider using new_local_repository instead.", + local = True, +) + +def _new_local_repository_impl(rctx): + if (rctx.attr.build_file == None) == (rctx.attr.build_file_content == _UNSET): + fail("exactly one of `build_file` and `build_file_content` must be specified") + + children = _get_dir_path(rctx).readdir() + for child in children: + rctx.symlink(child, child.basename) + + # On Windows, `rctx.symlink` actually does a copy for files (for directories, it uses + # junctions which basically behave like symlinks as far as we're concerned). So we need to + # watch the symlink target as well. + if rctx.os.name.startswith("windows") and not child.is_dir: + rctx.watch(child) + + if rctx.attr.build_file != None: + rctx.symlink(rctx.attr.build_file, "BUILD.bazel") + if rctx.os.name.startswith("windows"): + rctx.watch(rctx.attr.build_file) # same reason as above + else: + rctx.file("BUILD.bazel", rctx.attr.build_file_content) + +new_local_repository = repository_rule( + implementation = _new_local_repository_impl, + attrs = { + "path": attr.string( + doc = + "The path to the directory to make available as a repo.

The path can be " + + "either absolute, or relative to the workspace root.", + mandatory = True, + ), + "build_file": attr.label( + doc = + "A file to use as a BUILD file for this repo.

Exactly one of " + + "build_file and build_file_content must be specified. " + + "

The file addressed by this label does not need to be named BUILD, but can " + + "be. Something like BUILD.new-repo-name may work well to " + + "distinguish it from actual BUILD files.", + ), + "build_file_content": attr.string( + doc = + "The content of the BUILD file to be created for this repo.

Exactly one of " + + "build_file and build_file_content must be specified.", + default = _UNSET, + ), + }, + doc = + "Makes a local directory that doesn't contain Bazel files available as a repo. This " + + "directory need not contain Bazel BUILD files or a repo boundary file; they will be " + + "created by this repo rule. If the directory already contains Bazel files, consider " + + "using local_repository instead.", + local = True, +)