Dangling symlinks with custom rules with outputs
- Dominant language
- Java
- Stars
- 25.8k
- Forks
- 4.6k
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 75
Description
### Description of the bug:
External repo + build in the path + custom rules in starlark with outputs + NOT specifying `--experimental_sibling_repository_layout` can result in dangled symlinks.
With the following repo set up:
```
bazel-test-repo $ tree .
.
├── bazel-tree-contents
├── build -> xxxxx
├── BUILD
├── BUILD.external_repo.bzl
├── external_repo
│ └── test.sh
├── MODULE.bazel
├── MODULE.bazel.lock
├── my-build -> xxxxx/
├── my_test_rules.bzl
├── repo.bzl
└── xxxxx
└── BUILD
5 directories, 9 files
```
```
───────┬────────────────────────────────────────────────────────────────────────
│ File: .bazelversion
───────┼────────────────────────────────────────────────────────────────────────
1 + │ 9.0.0
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: .gitignore
───────┼────────────────────────────────────────────────────────────────────────
1 + │ bazel-*
2 + │ /MODULE.bazel.lock
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: BUILD
───────┼────────────────────────────────────────────────────────────────────────
1 + │ # Keep this file so that this folder is a package
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: BUILD.external_repo.bzl
───────┼────────────────────────────────────────────────────────────────────────
1 + │ exports_files(["test.sh"])
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: MODULE.bazel
───────┼────────────────────────────────────────────────────────────────────────
1 + │ external_repos = use_extension("//:repo.bzl", "external_repos")
2 + │
3 + │ use_repo(
4 + │ external_repos,
5 + │ "external_repo",
6 + │ )
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: external_repo/test.sh
───────┼────────────────────────────────────────────────────────────────────────
1 + │ echo external hello
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: my_test_rules.bzl
───────┼────────────────────────────────────────────────────────────────────────
1 + │ def _my_test_rule_impl(ctx):
2 + │ script_file = ctx.actions.declare_file(ctx.label.name + ".sh")
3 + │ script_contents = [
4 + │ "#!/bin/bash",
5 + │ "set -e",
6 + │ "cp -rf $1 $2",
7 + │ ]
8 + │ script_contents.append("")
9 + │
10 + │ ctx.actions.write(
11 + │ output = script_file,
12 + │ content = "\n".join(script_contents),
13 + │ is_executable = True,
14 + │ )
15 + │
16 + │ out_file = ctx.outputs.test_out
17 + │
18 + │ args = ctx.actions.args()
19 + │ args.add(ctx.attr.src[DefaultInfo].files.to_list()[0])
20 + │ args.add(out_file)
21 + │
22 + │ ctx.actions.run(
23 + │ outputs = [out_file],
24 + │ inputs = ctx.attr.src[DefaultInfo].files.to_list(),
25 + │ executable = script_file,
26 + │ arguments = [args],
27 + │ mnemonic = "TestAction",
28 + │ )
29 + │
30 + │ transitive_files = [ctx.attr.src[DefaultInfo].files]
31 + │ files = depset([script_file, out_file], transitive = transitive_files)
32 + │ return [DefaultInfo(
33 + │ files = files,
34 + │ runfiles = ctx.runfiles(transitive_files = files, collect_default = True),
35 + │ )]
36 + │
37 + │ my_test_rule = rule(
38 + │ implementation = _my_test_rule_impl,
39 + │ attrs = {
40 + │ "src": attr.label(allow_files = True),
41 + │ },
42 + │ outputs = {
43 + │ "test_out": "%{name}.test",
44 + │ },
45 + │ )
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: repo.bzl
───────┼────────────────────────────────────────────────────────────────────────
1 + │ load("@bazel_tools//tools/build_defs/repo:local.bzl", "new_local_repository")
2 + │
3 + │ def _external_repos_impl(_ctx):
4 + │ new_local_repository(
5 + │ name = "external_repo",
6 + │ path = "external_repo",
7 + │ build_file = "//:BUILD.external_repo.bzl",
8 + │ )
9 + │
10 + │ external_repos = module_extension(
11 + │ implementation = _external_repos_impl,
12 + │ )
13 + │
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
│ File: xxxxx/BUILD
───────┼────────────────────────────────────────────────────────────────────────
1 + │ load("//:my_test_rules.bzl", "my_test_rule")
2 + │
3 + │ my_test_rule(
4 + │ name = "my_target",
5 + │ src = "@external_repo//:test.sh",
6 + │ )
7 + │
8 + │ my_test_rule(
9 + │ name = "my_target2",
10 + │ src = ":my_target.test",
11 + │ )
───────┴────────────────────────────────────────────────────────────────────────
```
When I run `rm -rf MODULE.bazel.lock && bazel clean --expunge && rm -rf MODULE.bazel.lock && bazel clean --expunge && bazel build //my-build:my_target2 --sandbox_debug` or `rm -rf MODULE.bazel.lock && bazel clean --expunge && rm -rf MODULE.bazel.lock && bazel clean --expunge && bazel build //xxxxx:my_target2 --sandbox_debug`, the build succeeds. Note that the target label doesn't have `build` in its path components. When I run `rm -rf MODULE.bazel.lock && bazel clean --expunge && rm -rf MODULE.bazel.lock && bazel clean --expunge && bazel build //build:my_target2 --sandbox_debug`, it fails with the following error log:
```
ERROR: $HOME/src/bazel-test-repo/build/BUILD:3:13: output 'build/my_target.test' is a dangling symbolic link
ERROR: $HOME/src/bazel-test-repo/build/BUILD:3:13: TestAction build/my_target.test failed: not all outputs were created or valid
```
The same error happens if I change `build/BUILD` file to `build/xxx/BUILD` file, and build the `build/xxx:my_target2` target.
### Which category does this issue belong to?
_No response_
### What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.
It's in the description.
### Which operating system are you running Bazel on?
Linux
### What is the output of `bazel info release`?
release 9.0.0
### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.
_No response_
### What's the output of `git remote get-url origin; git rev-parse HEAD` ?
```text
error: No such remote 'origin'
HEAD
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git [...] -- [...]
```
### If this is a regression, please try to identify the Bazel commit where the bug was introduced with bazelisk --bisect.
_No response_
### Have you found anything relevant by searching the web?
_No response_
### Any other information, logs, or outputs that you want to share?
_No response_
Contributor guide
Research direction
Start with the reproducer files xxxxx/BUILD, build/BUILD, my_test_rules.bzl, and repo.bzl. Run the documented clean-and-build commands for //build:my_target2 and the non-failing targets, then compare the generated outputs and logs; done means the build no longer reports build/my_target.test as a dangling symbolic link.
Written by the indexing model from the issue text.
Assessment
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100