Add make-location lint for deprecated $(location) make variables (#1484)
## Summary
- Add a new buildifier lint category, `make-location`, that flags
deprecated `$(location)` and `$(locations)` make variables in BUILD
files.
- Dogfood the rule in this repo by replacing existing uses with
`$(execpath ...)` and enforcing the lint via `//:make_location_lint`.
## Background / deprecation context
Bazel's [`$(location)`](https://bazel.build/reference/be/make-variables)
and [`$(locations)`](https://bazel.build/reference/be/make-variables)
make variables are legacy pre-Starlark synonyms for
[`$(execpath)`](https://bazel.build/reference/be/make-variables) and
[`$(rootpath)`](https://bazel.build/reference/be/make-variables). Which
path they expand to depends on the attribute being expanded, which makes
behavior hard to predict and easy to get wrong.
From the [Make
Variables](https://bazel.build/reference/be/make-variables) reference:
> **`location`**: A synonym for either `execpath` or `rootpath`,
depending on the attribute being expanded. This is legacy pre-Starlark
behavior and **not recommended** unless you really know what it does for
a particular rule. See
[#2475](https://github.com/bazelbuild/bazel/issues/2475) for details.
The underlying inconsistency is discussed in
[bazelbuild/bazel#2475](https://github.com/bazelbuild/bazel/issues/2475)
(e.g. `$(location)` expanding to an exec path in some attributes but a
runfiles path in others). Bazel's docs now steer users toward explicit
variables:
- [`$(execpath ...)`](https://bazel.build/reference/be/make-variables) —
path under the execroot where build actions run
- [`$(rootpath ...)`](https://bazel.build/reference/be/make-variables) —
runfiles-relative path for runtime lookup (prefer [`$(rlocationpath
...)`](https://bazel.build/reference/be/make-variables) for
cross-platform runfiles)
This lint nudges BUILD authors toward those explicit forms instead of
the ambiguous legacy alias.
## Changes
- New `make-location` warning in `warn/warn_bazel.go` (BUILD files only;
does not flag `load("location", ...)`).
- Tests, `WARNINGS.md` / `warnings.textproto` docs, and warning-list
updates.
- Repo fixes: `buildifier/BUILD.bazel`, `buildozer/BUILD.bazel`,
`build/build_defs.bzl`.
- New `buildifier_test` target `//:make_location_lint` added to
`//:tests`.
## Test plan
- [x] `bazel test //warn:warn_test
--test_filter=TestMakeLocationVariable`
- [x] `bazel test //warn/docs:docs_test`
- [x] `bazel test //buildifier/config:config_test`
- [x] `bazel test //:make_location_lint`diff --git a/BUILD.bazel b/BUILD.bazel
index bc2fc5a..e58944a 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,9 +1,10 @@
load("@bazel_gazelle//:def.bzl", "gazelle")
-load("//buildifier:def.bzl", "buildifier")
+load("//buildifier:def.bzl", "buildifier", "buildifier_test")
exports_files([
"LICENSE",
"launcher.js",
+ "WORKSPACE",
])
config_setting(
@@ -20,6 +21,7 @@
test_suite(
name = "tests",
tests = [
+ ":make_location_lint",
"//api_proto:api.gen.pb.go_checkshtest",
"//build:build_test",
"//build_proto:build.gen.pb.go_checkshtest",
@@ -45,3 +47,11 @@
buildifier(
name = "buildifier",
)
+
+buildifier_test(
+ name = "make_location_lint",
+ lint_mode = "warn",
+ lint_warnings = ["make-location"],
+ no_sandbox = True,
+ workspace = "//:WORKSPACE",
+)
diff --git a/WARNINGS.md b/WARNINGS.md
index fe55dd0..3e3dab2 100644
--- a/WARNINGS.md
+++ b/WARNINGS.md
@@ -38,6 +38,7 @@
* [`list-append`](#list-append)
* [`load`](#load)
* [`load-on-top`](#load-on-top)
+ * [`make-location`](#make-location)
* [`module-docstring`](#module-docstring)
* [`name-conventions`](#name-conventions)
* [`native-android`](#native-android)
@@ -761,6 +762,36 @@
--------------------------------------------------------------------------------
+## <a name="make-location"></a>The `$(location)` make variable is deprecated
+
+ * Category name: `make-location`
+ * Automatic fix: no
+ * [Suppress the warning](#suppress): `# buildifier: disable=make-location`
+
+The `$(location)` and `$(locations)` make variables are legacy synonyms for
+`$(execpath)` and `$(rootpath)` whose behavior depends on the attribute being
+expanded. Use `$(execpath ...)` when you need the execution path, or
+`$(rootpath ...)` when you need the runfiles path.
+
+Examples that trigger this warning:
+
+```python
+genrule(
+ name = "example",
+ srcs = [":input"],
+ outs = ["output"],
+ cmd = "cp $(location :input) $@",
+)
+```
+
+Instead, use an explicit make variable:
+
+```python
+cmd = "cp $(execpath :input) $@",
+```
+
+--------------------------------------------------------------------------------
+
## <a name="module-docstring"></a>The file has no module docstring
* Category name: `module-docstring`
diff --git a/build/build_defs.bzl b/build/build_defs.bzl
index 2a2c489..900f8b8 100644
--- a/build/build_defs.bzl
+++ b/build/build_defs.bzl
@@ -144,7 +144,7 @@
srcs = [src + "_check.sh"],
deps = ["@bazel_tools//tools/bash/runfiles"],
data = [src, gen],
- args = ["$(location " + src + ")", "$(location " + gen + ")"],
+ args = ["$(rootpath " + src + ")", "$(rootpath " + gen + ")"],
)
# magic copy rule used to update the checked-in version
@@ -152,7 +152,7 @@
name = src + "_copysh",
srcs = [gen],
outs = [src + "copy.sh"],
- cmd = "echo 'cp $${BUILD_WORKSPACE_DIRECTORY}/$(location " + gen +
+ cmd = "echo 'cp $${BUILD_WORKSPACE_DIRECTORY}/$(execpath " + gen +
") $${BUILD_WORKSPACE_DIRECTORY}/" + native.package_name() + "/" + src + "' > $@",
)
sh_binary(
diff --git a/buildifier/BUILD.bazel b/buildifier/BUILD.bazel
index 4b27215..b248f58 100644
--- a/buildifier/BUILD.bazel
+++ b/buildifier/BUILD.bazel
@@ -93,7 +93,7 @@
size = "small",
srcs = ["integration_test.sh"],
args = [
- "$(location :buildifier)",
+ "$(rootpath :buildifier)",
],
data = [
":buildifier",
diff --git a/buildifier/config/config_test.go b/buildifier/config/config_test.go
index ff614fb..ca31dd6 100644
--- a/buildifier/config/config_test.go
+++ b/buildifier/config/config_test.go
@@ -77,6 +77,7 @@
// "keyword-positional-params",
// "list-append",
// "load",
+ // "make-location",
// "module-docstring",
// "name-conventions",
// "native-android",
@@ -301,6 +302,7 @@
"keyword-positional-params",
"list-append",
"load",
+ "make-location",
"module-docstring",
"name-conventions",
"native-android",
@@ -403,6 +405,7 @@
"keyword-positional-params",
"list-append",
"load",
+ "make-location",
"module-docstring",
"name-conventions",
"native-android",
@@ -505,6 +508,7 @@
"keyword-positional-params",
"list-append",
"load",
+ "make-location",
"module-docstring",
"name-conventions",
"native-android",
@@ -607,6 +611,7 @@
"keyword-positional-params",
"list-append",
"load",
+ "make-location",
"module-docstring",
"name-conventions",
"native-android",
diff --git a/buildifier/integration_test.sh b/buildifier/integration_test.sh
index 2bf906c..fb7f323 100755
--- a/buildifier/integration_test.sh
+++ b/buildifier/integration_test.sh
@@ -311,6 +311,7 @@
"keyword-positional-params",
"list-append",
"load",
+ "make-location",
"module-docstring",
"name-conventions",
"native-android",
diff --git a/buildozer/BUILD.bazel b/buildozer/BUILD.bazel
index 7815553..667b178 100644
--- a/buildozer/BUILD.bazel
+++ b/buildozer/BUILD.bazel
@@ -28,7 +28,7 @@
size = "small",
srcs = ["buildozer_test.sh"],
args = [
- "$(location :buildozer)",
+ "$(rootpath :buildozer)",
],
data = [
"test_common.sh",
diff --git a/warn/docs/warnings.textproto b/warn/docs/warnings.textproto
index 8be362e..ab26893 100644
--- a/warn/docs/warnings.textproto
+++ b/warn/docs/warnings.textproto
@@ -502,6 +502,30 @@
}
warnings: {
+ name: "make-location"
+ header: "The `$(location)` make variable is deprecated"
+ description:
+ "The `$(location)` and `$(locations)` make variables are legacy synonyms for\n"
+ "`$(execpath)` and `$(rootpath)` whose behavior depends on the attribute being\n"
+ "expanded. Use `$(execpath ...)` when you need the execution path, or\n"
+ "`$(rootpath ...)` when you need the runfiles path.\n\n"
+ "Examples that trigger this warning:\n\n"
+ "```python\n"
+ "genrule(\n"
+ " name = \"example\",\n"
+ " srcs = [\":input\"],\n"
+ " outs = [\"output\"],\n"
+ " cmd = \"cp $(location :input) $@\",\n"
+ ")\n"
+ "```\n\n"
+ "Instead, use an explicit make variable:\n\n"
+ "```python\n"
+ "cmd = \"cp $(execpath :input) $@\",\n"
+ "```"
+ autofix: false
+}
+
+warnings: {
name: "module-docstring"
header: "The file has no module docstring"
description:
diff --git a/warn/warn.go b/warn/warn.go
index 82d5a42..a221ff4 100644
--- a/warn/warn.go
+++ b/warn/warn.go
@@ -146,6 +146,7 @@
"keyword-positional-params": keywordPositionalParametersWarning,
"list-append": listAppendWarning,
"load": unusedLoadWarning,
+ "make-location": makeLocationVariableWarning,
"module-docstring": moduleDocstringWarning,
"name-conventions": nameConventionsWarning,
"native-build": nativeInBuildFilesWarning,
diff --git a/warn/warn_bazel.go b/warn/warn_bazel.go
index be6000c..555e4aa 100644
--- a/warn/warn_bazel.go
+++ b/warn/warn_bazel.go
@@ -20,11 +20,15 @@
import (
"fmt"
+ "regexp"
"strings"
"github.com/bazelbuild/buildtools/build"
)
+// locationMakeVariableRe matches deprecated $(location) and $(locations) make variables.
+var locationMakeVariableRe = regexp.MustCompile(`\$\(locations?(?:\s[^)]*)?\)`)
+
func constantGlobPatternWarning(patterns *build.ListExpr) []*LinterFinding {
findings := []*LinterFinding{}
for _, expr := range patterns.List {
@@ -256,6 +260,26 @@
return findings
}
+func makeLocationVariableWarning(f *build.File) []*LinterFinding {
+ if f.Type != build.TypeBuild {
+ return nil
+ }
+
+ findings := []*LinterFinding{}
+ build.Walk(f, func(expr build.Expr, stack []build.Expr) {
+ stringExpr, ok := expr.(*build.StringExpr)
+ if !ok {
+ return
+ }
+ if locationMakeVariableRe.MatchString(stringExpr.Value) {
+ findings = append(findings,
+ makeLinterFinding(stringExpr,
+ `The "$(location)" and "$(locations)" make variables are deprecated. Use "$(execpath ...)" or "$(rootpath ...)" instead.`))
+ }
+ })
+ return findings
+}
+
func externalPathWarning(f *build.File) []*LinterFinding {
if f.Type == build.TypeDefault {
// Only applicable to Bazel files
diff --git a/warn/warn_bazel_test.go b/warn/warn_bazel_test.go
index 9201cea..d6e495c 100644
--- a/warn/warn_bazel_test.go
+++ b/warn/warn_bazel_test.go
@@ -289,3 +289,41 @@
},
scopeBazel)
}
+
+func TestMakeLocationVariable(t *testing.T) {
+ checkFindings(t, "make-location", `
+genrule(
+ name = "a",
+ srcs = [":foo"],
+ outs = ["out"],
+ cmd = "cp $(location :foo) $@",
+)
+
+genrule(
+ name = "b",
+ srcs = [":a", ":b"],
+ outs = ["out"],
+ cmd = "cat $(locations :a :b) > $@",
+)
+
+genrule(
+ name = "c",
+ srcs = [":foo"],
+ outs = ["out"],
+ cmd = "cp $$(location :foo) $$@",
+)
+
+cc_test(
+ name = "d",
+ args = ["--config=$(execpath :cfg)"],
+)
+
+load("location", "symbol")
+`,
+ []string{
+ `:5: The "$(location)" and "$(locations)" make variables are deprecated. Use "$(execpath ...)" or "$(rootpath ...)" instead.`,
+ `:12: The "$(location)" and "$(locations)" make variables are deprecated. Use "$(execpath ...)" or "$(rootpath ...)" instead.`,
+ `:19: The "$(location)" and "$(locations)" make variables are deprecated. Use "$(execpath ...)" or "$(rootpath ...)" instead.`,
+ },
+ scopeBuild)
+}