Make `path-mapping` messages specific and fix simple cases automatically Each kind of path string (`File.path`, `File.dirname`, `File.root.path`, `ctx.bin_dir.path`) now gets its own message tailored to where it is used. Simple cases are fixed automatically: a `File.path` passed to `args.add()`, `args.add_all()`, `args.add_joined()` or `executable`, optionally formatted into a string literal or wrapped in a list comprehension, is replaced by the `File` object and a `format` or `format_each` parameter. Variables created with `ctx.actions.declare_directory()` are passed with `expand_directories = False` since `args.add()` rejects directories.
diff --git a/WARNINGS.md b/WARNINGS.md index 989440a..82cbc65 100644 --- a/WARNINGS.md +++ b/WARNINGS.md
@@ -1204,7 +1204,7 @@ ## <a name="path-mapping"></a>Action command lines should not contain path strings * Category name: `path-mapping` - * Automatic fix: no + * Automatic fix: yes * [Disabled by default](buildifier/README.md#linter) * [Suppress the warning](#suppress): `# buildifier: disable=path-mapping` @@ -1227,6 +1227,8 @@ ```python args = ctx.actions.args() args.add(src.path) +args.add("--include=" + hdr.path) +args.add_all([src.path for src in srcs]) args.add(dir.path) args.add(src.dirname) ctx.actions.run_shell( @@ -1244,6 +1246,8 @@ args = ctx.actions.args() args.add(src) +args.add(hdr, format = "--include=%s") +args.add_all(srcs) args.add_all([dir], expand_directories = False) args.add_all([src], map_each = _dirname) ctx.actions.run( @@ -1253,6 +1257,14 @@ ) ``` +Simple cases are fixed automatically: a `File.path` passed to `args.add()`, +`args.add_all()`, `args.add_joined()` or `executable`, optionally formatted +into a string literal or wrapped in a list comprehension, is replaced by the +`File` object. Variables assigned from `ctx.actions.declare_directory()` are +passed with `expand_directories = False`. Other directories have to be fixed +manually, since `args.add()` rejects them and `args.add_all()` expands them by +default. + -------------------------------------------------------------------------------- ## <a name="positional-args"></a>Keyword arguments should be used over positional arguments
diff --git a/warn/docs/warnings.textproto b/warn/docs/warnings.textproto index 8c8ec28..7b57841 100644 --- a/warn/docs/warnings.textproto +++ b/warn/docs/warnings.textproto
@@ -885,6 +885,8 @@ "```python\n" "args = ctx.actions.args()\n" "args.add(src.path)\n" + "args.add(\"--include=\" + hdr.path)\n" + "args.add_all([src.path for src in srcs])\n" "args.add(dir.path)\n" "args.add(src.dirname)\n" "ctx.actions.run_shell(\n" @@ -899,6 +901,8 @@ " return file.dirname\n\n" "args = ctx.actions.args()\n" "args.add(src)\n" + "args.add(hdr, format = \"--include=%s\")\n" + "args.add_all(srcs)\n" "args.add_all([dir], expand_directories = False)\n" "args.add_all([src], map_each = _dirname)\n" "ctx.actions.run(\n" @@ -906,8 +910,15 @@ " executable = cp,\n" " arguments = [args],\n" ")\n" - "```" - autofix: false + "```\n\n" + "Simple cases are fixed automatically: a `File.path` passed to `args.add()`,\n" + "`args.add_all()`, `args.add_joined()` or `executable`, optionally formatted\n" + "into a string literal or wrapped in a list comprehension, is replaced by the\n" + "`File` object. Variables assigned from `ctx.actions.declare_directory()` are\n" + "passed with `expand_directories = False`. Other directories have to be fixed\n" + "manually, since `args.add()` rejects them and `args.add_all()` expands them by\n" + "default." + autofix: true } warnings: {
diff --git a/warn/warn_bazel.go b/warn/warn_bazel.go index c87ddbc..a082968 100644 --- a/warn/warn_bazel.go +++ b/warn/warn_bazel.go
@@ -280,12 +280,84 @@ return findings } -// pathStringAttrs lists attributes that return a path string which may contain a configuration -// prefix (such as `bazel-out/k8-fastbuild/bin`) and is therefore not rewritten by path mapping: -// `File.path`, `File.dirname`, `File.root.path`, `ctx.bin_dir.path` and `ctx.genfiles_dir.path`. -var pathStringAttrs = map[string]bool{ - "path": true, - "dirname": true, +// pathStringKind classifies expressions that return a path string which may contain a +// configuration prefix (such as `bazel-out/k8-fastbuild/bin`) and is therefore not rewritten by +// path mapping. +type pathStringKind int + +const ( + notPathString pathStringKind = iota + filePath // `file.path` + fileDirname // `file.dirname` + fileRootPath // `file.root.path` + ctxDirPath // `ctx.bin_dir.path` or `ctx.genfiles_dir.path` +) + +// pathStringContext describes where a path string is used. +type pathStringContext int + +const ( + argsContext pathStringContext = iota // `args.add()`, `args.add_all()` or `args.add_joined()` + executableContext // `executable` of `ctx.actions.run()` + argumentsContext // `arguments` of `ctx.actions.run()` or `ctx.actions.run_shell()` + commandContext // `command` of `ctx.actions.run_shell()` +) + +// classifyPathString returns the kind of path string the expression evaluates to. The parent is +// used to skip method calls such as `ctx.path(...)` or `paths.dirname(...)`. +func classifyPathString(expr, parent build.Expr) pathStringKind { + dot, ok := expr.(*build.DotExpr) + if !ok { + return notPathString + } + if call, ok := parent.(*build.CallExpr); ok && call.X == expr { + return notPathString + } + switch dot.Name { + case "dirname": + return fileDirname + case "path": + if inner, ok := dot.X.(*build.DotExpr); ok { + switch inner.Name { + case "root": + return fileRootPath + case "bin_dir", "genfiles_dir": + return ctxDirPath + } + } + return filePath + } + return notPathString +} + +// pathStringMessage builds a message for a path string of the given kind used in the given context. +func pathStringMessage(dot *build.DotExpr, kind pathStringKind, context pathStringContext, method string) string { + expr := build.FormatString(dot) + receiver := build.FormatString(dot.X) + msg := fmt.Sprintf("%q is not rewritten by path mapping. ", expr) + switch kind { + case fileDirname: + return msg + fmt.Sprintf(`Add %q to "ctx.actions.args()" with a "map_each" callback that returns "file.dirname" instead.`, receiver) + case fileRootPath: + file := build.FormatString(dot.X.(*build.DotExpr).X) + return msg + fmt.Sprintf(`Add %q to "ctx.actions.args()" with a "map_each" callback that returns "file.root.path" instead.`, file) + case ctxDirPath: + return msg + `Compute it from an output "File" in a "map_each" callback via "file.root.path" instead.` + } + switch context { + case argsContext: + if strings.HasSuffix(method, ".add") { + return msg + fmt.Sprintf(`Pass %q to "%s()" instead, or use "%s_all([%s], expand_directories = False)" if it is a directory.`, + receiver, method, method, receiver) + } + return msg + fmt.Sprintf(`Pass %q to "%s()" instead (with "expand_directories = False" if it is a directory).`, receiver, method) + case executableContext: + return msg + fmt.Sprintf(`Pass %q as "executable" instead.`, receiver) + case argumentsContext: + return msg + fmt.Sprintf(`Add %q to "ctx.actions.args()" and pass the "Args" object in "arguments" instead.`, receiver) + default: + return msg + fmt.Sprintf(`Add %q to "ctx.actions.args()", pass the "Args" object in "arguments" and refer to it as "$1", "$2", ... in "command" instead.`, receiver) + } } // isCtxActionsArgs reports whether expr is a `ctx.actions.args()` object. Besides the detected @@ -298,6 +370,279 @@ return ok && ident.Name == "args" } +// collectDeclaredDirectories returns the names of all variables that are assigned the result of +// `ctx.actions.declare_directory()` anywhere in the file. +func collectDeclaredDirectories(f *build.File) map[string]bool { + directories := make(map[string]bool) + build.Walk(f, func(expr build.Expr, stack []build.Expr) { + assign, ok := expr.(*build.AssignExpr) + if !ok { + return + } + ident, ok := assign.LHS.(*build.Ident) + if !ok { + return + } + call, ok := assign.RHS.(*build.CallExpr) + if !ok { + return + } + if dot, ok := call.X.(*build.DotExpr); ok && dot.Name == "declare_directory" { + directories[ident.Name] = true + } + }) + return directories +} + +// isValidArgsFormat reports whether str can be used as a `format` parameter of +// `ctx.actions.args()` methods, i.e. it contains exactly one `%s` and no other `%`. +func isValidArgsFormat(str string) bool { + return strings.Count(str, "%") == 1 && strings.Contains(str, "%s") +} + +// splitFormattedPath checks whether expr formats a single `file.path` expression into a string +// literal, e.g. `"-I%s" % file.path`, `"-I" + file.path`, `file.path + ".d"` or +// `"-I{}".format(file.path)`. It returns the equivalent `format` string for `ctx.actions.args()` +// and the formatted `file.path` expression. +func splitFormattedPath(expr build.Expr) (string, *build.DotExpr, bool) { + switch node := expr.(type) { + case *build.ParenExpr: + return splitFormattedPath(node.X) + case *build.BinaryExpr: + switch node.Op { + case "%": + str, ok := node.X.(*build.StringExpr) + if !ok || !isValidArgsFormat(str.Value) { + return "", nil, false + } + dot, ok := node.Y.(*build.DotExpr) + if !ok || classifyPathString(dot, nil) != filePath { + return "", nil, false + } + return str.Value, dot, true + case "+": + var format strings.Builder + var dot *build.DotExpr + for _, operand := range flattenConcatenation(node) { + switch operand := operand.(type) { + case *build.StringExpr: + if strings.Contains(operand.Value, "%") { + return "", nil, false + } + format.WriteString(operand.Value) + case *build.DotExpr: + if dot != nil || classifyPathString(operand, nil) != filePath { + return "", nil, false + } + dot = operand + format.WriteString("%s") + default: + return "", nil, false + } + } + if dot == nil { + return "", nil, false + } + return format.String(), dot, true + } + case *build.CallExpr: + method, ok := node.X.(*build.DotExpr) + if !ok || method.Name != "format" || len(node.List) != 1 { + return "", nil, false + } + str, ok := method.X.(*build.StringExpr) + if !ok || strings.Count(str.Value, "{}") != 1 || strings.Count(str.Value, "{")+strings.Count(str.Value, "}") != 2 || strings.Contains(str.Value, "%") { + return "", nil, false + } + dot, ok := node.List[0].(*build.DotExpr) + if !ok || classifyPathString(dot, nil) != filePath { + return "", nil, false + } + return strings.Replace(str.Value, "{}", "%s", 1), dot, true + } + return "", nil, false +} + +// flattenConcatenation returns the operands of a chain of `+` operations. +func flattenConcatenation(expr build.Expr) []build.Expr { + if binary, ok := expr.(*build.BinaryExpr); ok && binary.Op == "+" { + return append(flattenConcatenation(binary.X), flattenConcatenation(binary.Y)...) + } + if paren, ok := expr.(*build.ParenExpr); ok { + return flattenConcatenation(paren.X) + } + return []build.Expr{expr} +} + +// simpleComprehension checks whether expr is a list comprehension of the form `[body for var in seq]` +// and returns the loop variable, the body and the sequence. +func simpleComprehension(expr build.Expr) (*build.Ident, build.Expr, build.Expr, bool) { + comp, ok := expr.(*build.Comprehension) + if !ok || comp.Curly || len(comp.Clauses) != 1 { + return nil, nil, nil, false + } + forClause, ok := comp.Clauses[0].(*build.ForClause) + if !ok { + return nil, nil, nil, false + } + loopVar, ok := forClause.Vars.(*build.Ident) + if !ok { + return nil, nil, nil, false + } + return loopVar, comp.Body, forClause.X, true +} + +// isPathOf reports whether expr is `<ident>.path` for the given identifier. +func isPathOf(expr build.Expr, ident *build.Ident) (*build.DotExpr, bool) { + dot, ok := expr.(*build.DotExpr) + if !ok || classifyPathString(dot, nil) != filePath { + return nil, false + } + x, ok := dot.X.(*build.Ident) + return dot, ok && x.Name == ident.Name +} + +// pathMappingFixer collects fixes for a single `args.add()`, `args.add_all()` or `args.add_joined()` +// call. A fix either replaces a single node (e.g. `file.path` with `file`) or rewrites the whole +// call (e.g. to add a `format` parameter). +type pathMappingFixer struct { + call *build.CallExpr + callPtr *build.Expr + method string + directories map[string]bool + newList []build.Expr // modified copy of call.List + newMethod string // method name of the rewritten call, if it changes + rewriteCall bool // the whole call needs to be replaced + fixed map[*build.DotExpr]LinterReplacement // node-level fixes + broken bool // a safe fix is not possible +} + +func newPathMappingFixer(callPtr *build.Expr, call *build.CallExpr, method string, directories map[string]bool) *pathMappingFixer { + return &pathMappingFixer{ + call: call, + callPtr: callPtr, + method: method, + directories: directories, + newList: append([]build.Expr{}, call.List...), + newMethod: method, + fixed: make(map[*build.DotExpr]LinterReplacement), + } +} + +func (fx *pathMappingFixer) hasKeyword(name string) bool { + _, _, param := getParam(fx.call.List, name) + return param != nil +} + +func (fx *pathMappingFixer) isDirectory(expr build.Expr) bool { + ident, ok := expr.(*build.Ident) + return ok && fx.directories[ident.Name] +} + +func (fx *pathMappingFixer) addKeyword(name string, value build.Expr) { + fx.newList = append(fx.newList, makeKeyword(value, name)) + fx.rewriteCall = true +} + +// fixPositional tries to fix the i-th positional argument of the call. +func (fx *pathMappingFixer) fixPositional(i int) { + arg := fx.call.List[i] + switch fx.method { + case "add": + // `args.add(file.path)`, `args.add("--flag", file.path)`, `args.add("--flag=%s" % file.path)` + if dot, ok := arg.(*build.DotExpr); ok && classifyPathString(dot, nil) == filePath { + fx.newList[i] = dot.X + fx.fixed[dot] = LinterReplacement{&fx.call.List[i], dot.X} + if fx.isDirectory(dot.X) { + // `args.add()` doesn't accept directories, use `args.add_all([...], expand_directories = False)` + fx.newMethod = "add_all" + fx.rewriteCall = true + } + return + } + if format, dot, ok := splitFormattedPath(arg); ok && !fx.hasKeyword("format") && !fx.isDirectory(dot.X) { + fx.newList[i] = dot.X + fx.fixed[dot] = LinterReplacement{} + fx.addKeyword("format", &build.StringExpr{Value: format}) + } + case "add_all", "add_joined": + if i != 0 || fx.hasKeyword("map_each") { + return + } + switch values := arg.(type) { + case *build.ListExpr: + // `args.add_all([a.path, b.path])` + newValues := *values + newValues.List = append([]build.Expr{}, values.List...) + for j, value := range values.List { + dot, ok := value.(*build.DotExpr) + if !ok || classifyPathString(dot, nil) != filePath { + continue + } + newValues.List[j] = dot.X + fx.fixed[dot] = LinterReplacement{&values.List[j], dot.X} + if fx.isDirectory(dot.X) && !fx.hasKeyword("expand_directories") { + fx.rewriteCall = true + } + } + if fx.rewriteCall { + fx.newList[i] = &newValues + if !fx.hasKeyword("expand_directories") { + fx.addKeyword("expand_directories", &build.Ident{Name: "False"}) + } + } + case *build.Comprehension: + // `args.add_all([f.path for f in files])`, `args.add_all(["-I" + f.path for f in files])` + loopVar, body, seq, ok := simpleComprehension(values) + if !ok { + return + } + if dot, ok := isPathOf(body, loopVar); ok { + fx.newList[i] = seq + fx.fixed[dot] = LinterReplacement{&fx.call.List[i], seq} + return + } + format, dot, ok := splitFormattedPath(body) + if !ok || fx.hasKeyword("format_each") { + return + } + if _, ok := isPathOf(dot, loopVar); !ok { + return + } + fx.newList[i] = seq + fx.fixed[dot] = LinterReplacement{} + fx.addKeyword("format_each", &build.StringExpr{Value: format}) + } + } +} + +// replacement returns the fix for the given node, if any. +func (fx *pathMappingFixer) replacement(dot *build.DotExpr) []LinterReplacement { + fix, ok := fx.fixed[dot] + if !ok { + return nil + } + if !fx.rewriteCall { + return []LinterReplacement{fix} + } + if fx.newMethod != fx.method && fx.hasKeyword("format") { + // `args.add(dir.path, format = ...)` can't be converted to `args.add_all()` + return nil + } + newCall := *fx.call + newCall.List = fx.newList + if fx.newMethod != fx.method { + newDot := *fx.call.X.(*build.DotExpr) + newDot.Name = fx.newMethod + newCall.X = &newDot + newCall.List = []build.Expr{ + &build.ListExpr{List: fx.newList}, + makeKeyword(&build.Ident{Name: "False"}, "expand_directories"), + } + } + return []LinterReplacement{{fx.callPtr, &newCall}} +} + // pathMappingWarning reports path strings used in action command lines. Path mapping // (https://github.com/bazelbuild/bazel/discussions/22658) can only rewrite `File` objects passed to // `ctx.actions.args()`, so strings obtained from `File.path`, `File.dirname` etc. break actions @@ -308,29 +653,27 @@ } types := DetectTypes(f) + directories := collectDeclaredDirectories(f) findings := []*LinterFinding{} - // checkArgument reports every path string expression inside a command line argument. - checkArgument := func(arg build.Expr) { + // report reports every path string expression inside a command line argument. + report := func(arg build.Expr, context pathStringContext, method string, replacement func(dot *build.DotExpr) []LinterReplacement) { build.Walk(arg, func(expr build.Expr, stack []build.Expr) { - dot, ok := expr.(*build.DotExpr) - if !ok || !pathStringAttrs[dot.Name] { + var parent build.Expr + if len(stack) > 0 { + parent = stack[len(stack)-1] + } + kind := classifyPathString(expr, parent) + if kind == notPathString { return } - if len(stack) > 0 { - // Skip method calls such as `ctx.path(...)` or `paths.dirname(...)` - if call, ok := stack[len(stack)-1].(*build.CallExpr); ok && call.X == expr { - return - } - } - findings = append(findings, makeLinterFinding(dot, fmt.Sprintf( - `%q is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - build.FormatString(dot)))) + dot := expr.(*build.DotExpr) + findings = append(findings, makeLinterFinding(dot, pathStringMessage(dot, kind, context, method), replacement(dot)...)) }) } - build.Walk(f, func(expr build.Expr, stack []build.Expr) { - call, ok := expr.(*build.CallExpr) + build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) { + call, ok := (*expr).(*build.CallExpr) if !ok { return } @@ -347,26 +690,37 @@ default: return } - for _, arg := range call.List { - if assign, ok := arg.(*build.AssignExpr); ok { - arg = assign.RHS + fixer := newPathMappingFixer(expr, call, dot.Name, directories) + for i, arg := range call.List { + if _, ok := arg.(*build.AssignExpr); !ok { + fixer.fixPositional(i) } - checkArgument(arg) + } + for _, arg := range call.List { + report(makePositional(arg), argsContext, build.FormatString(dot), fixer.replacement) } case types[dot.X] == CtxActions: // `ctx.actions.run(...)` and `ctx.actions.run_shell(...)` - var params []string + noFix := func(*build.DotExpr) []LinterReplacement { return nil } switch dot.Name { case "run": - params = []string{"executable", "arguments"} + if _, _, param := getParam(call.List, "executable"); param != nil { + report(param.RHS, executableContext, dot.Name, func(dot *build.DotExpr) []LinterReplacement { + if dot == param.RHS && classifyPathString(dot, nil) == filePath { + return []LinterReplacement{{¶m.RHS, dot.X}} + } + return nil + }) + } + if _, _, param := getParam(call.List, "arguments"); param != nil { + report(param.RHS, argumentsContext, dot.Name, noFix) + } case "run_shell": - params = []string{"command", "arguments"} - default: - return - } - for _, name := range params { - if _, _, param := getParam(call.List, name); param != nil { - checkArgument(param.RHS) + if _, _, param := getParam(call.List, "command"); param != nil { + report(param.RHS, commandContext, dot.Name, noFix) + } + if _, _, param := getParam(call.List, "arguments"); param != nil { + report(param.RHS, argumentsContext, dot.Name, noFix) } } }
diff --git a/warn/warn_bazel_test.go b/warn/warn_bazel_test.go index 85a78be..dc3a4d1 100644 --- a/warn/warn_bazel_test.go +++ b/warn/warn_bazel_test.go
@@ -247,7 +247,7 @@ } func TestPathMapping(t *testing.T) { - checkFindings(t, "path-mapping", ` + checkFindingsAndFix(t, "path-mapping", ` load("@bazel_skylib//lib:paths.bzl", "paths") def _dirname(file): @@ -259,6 +259,7 @@ def _impl(ctx): args = ctx.actions.args() actions = ctx.actions + out_dir = ctx.actions.declare_directory("out") # Compatible with path mapping args.add(src) @@ -280,18 +281,42 @@ arguments = [args], ) - # Incompatible with path mapping + # Incompatible with path mapping, fixed automatically args.add(src.path) args.add("--out", out.path) - args.add(src.dirname) - args.add(dir.path) + args.add(src.path, format = "--src=%s") + args.add("--src=%s" % src.path) + args.add("--src=" + src.path) + args.add(src.path + ".d") + args.add("-I" + src.path + "/include") + args.add("--src={}".format(src.path)) + args.add(out_dir.path) + args.add("--out", out_dir.path) + args.add_all([src.path, out.path]) args.add_all([src.path for src in srcs]) + args.add_all([src.path for src in srcs], format_each = "-I%s") + args.add_all(["-I" + inc.path for inc in incs]) args.add_joined([src.path for src in srcs], join_with = ",") + args.add_all([src.path, out_dir.path]) + ctx.actions.run( + outputs = [out], + executable = tool.path, + arguments = [args], + ) + + # Incompatible with path mapping, not fixed automatically + args.add(src.dirname) args.add("--bin=%s" % ctx.bin_dir.path) args.add("--gen={}".format(ctx.genfiles_dir.path)) args.add("--root=" + src.root.path) args.add(paths.join(ctx.bin_dir.path, "include")) args.add(src.path if src else out.path) + args.add("--src=%s" % src.path, format = "%s") + args.add("%s=%s" % (name, src.path)) + args.add(out_dir.path, format = "--out=%s") + args.add_all([src.path for src in srcs if src]) + args.add_all([src.path for src in srcs], map_each = _dirname) + args.add_all(["-I" + inc.path for inc in incs], format_each = "%s") ctx.actions.run( outputs = [out], executable = tool.path, @@ -316,28 +341,142 @@ arguments = [args], progress_message = "Building %s" % out.path, ) +`, ` +load("@bazel_skylib//lib:paths.bzl", "paths") + +def _dirname(file): + return file.dirname + +def _add_srcs(args, srcs): + args.add_all(srcs) + +def _impl(ctx): + args = ctx.actions.args() + actions = ctx.actions + out_dir = ctx.actions.declare_directory("out") + + # Compatible with path mapping + args.add(src) + args.add("--out", out) + args.add_all(dirs, expand_directories = False) + args.add_all([src], map_each = _dirname) + args.add_all(srcs, format_each = "--src=%s") + args.add(src.short_path) + args.add(src.basename) + args.add(paths.dirname(src.short_path)) + ctx.actions.run( + outputs = [out], + executable = ctx.executable._tool, + arguments = [args], + ) + ctx.actions.run_shell( + outputs = [out], + command = "cp $1 $2", + arguments = [args], + ) + + # Incompatible with path mapping, fixed automatically + args.add(src) + args.add("--out", out) + args.add(src, format = "--src=%s") + args.add(src, format = "--src=%s") + args.add(src, format = "--src=%s") + args.add(src, format = "%s.d") + args.add(src, format = "-I%s/include") + args.add(src, format = "--src=%s") + args.add_all([out_dir], expand_directories = False) + args.add_all(["--out", out_dir], expand_directories = False) + args.add_all([src, out]) + args.add_all(srcs) + args.add_all(srcs, format_each = "-I%s") + args.add_all(incs, format_each = "-I%s") + args.add_joined(srcs, join_with = ",") + args.add_all([src, out_dir], expand_directories = False) + ctx.actions.run( + outputs = [out], + executable = tool, + arguments = [args], + ) + + # Incompatible with path mapping, not fixed automatically + args.add(src.dirname) + args.add("--bin=%s" % ctx.bin_dir.path) + args.add("--gen={}".format(ctx.genfiles_dir.path)) + args.add("--root=" + src.root.path) + args.add(paths.join(ctx.bin_dir.path, "include")) + args.add(src.path if src else out.path) + args.add("--src=%s" % src.path, format = "%s") + args.add("%s=%s" % (name, src.path)) + args.add(out_dir.path, format = "--out=%s") + args.add_all([src.path for src in srcs if src]) + args.add_all([src.path for src in srcs], map_each = _dirname) + args.add_all(["-I" + inc.path for inc in incs], format_each = "%s") + ctx.actions.run( + outputs = [out], + executable = tool, + arguments = [src.path, "--out", out.path], + ) + ctx.actions.run_shell( + outputs = [out], + command = "cp %s %s" % (src.path, out.path), + ) + actions.run_shell( + outputs = [out], + command = "cp $1 " + out.dirname, + arguments = [src.path], + ) + + # Unrelated + other.add(src.path) + ctx.actions.other(arguments = [src.path]) + ctx.actions.run( + outputs = [out], + executable = ctx.executable._tool, + arguments = [args], + progress_message = "Building %s" % out.path, + ) `, []string{ - `:7: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:34: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:35: "out.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:36: "src.dirname" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:37: "dir.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:38: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:39: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:40: "ctx.bin_dir.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:41: "ctx.genfiles_dir.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:42: "src.root.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:43: "ctx.bin_dir.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:44: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:44: "out.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:47: "tool.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:48: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:48: "out.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:52: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:52: "out.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:56: "out.dirname" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, - `:57: "src.path" is a path string that is not rewritten by path mapping. Pass the "File" object to "ctx.actions.args()" or compute the path in a "map_each" callback instead.`, + `:7: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:35: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:36: "out.path" is not rewritten by path mapping. Pass "out" to "args.add()" instead, or use "args.add_all([out], expand_directories = False)" if it is a directory.`, + `:37: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:38: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:39: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:40: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:41: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:42: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:43: "out_dir.path" is not rewritten by path mapping. Pass "out_dir" to "args.add()" instead, or use "args.add_all([out_dir], expand_directories = False)" if it is a directory.`, + `:44: "out_dir.path" is not rewritten by path mapping. Pass "out_dir" to "args.add()" instead, or use "args.add_all([out_dir], expand_directories = False)" if it is a directory.`, + `:45: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:45: "out.path" is not rewritten by path mapping. Pass "out" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:46: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:47: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:48: "inc.path" is not rewritten by path mapping. Pass "inc" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:49: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_joined()" instead (with "expand_directories = False" if it is a directory).`, + `:50: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:50: "out_dir.path" is not rewritten by path mapping. Pass "out_dir" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:53: "tool.path" is not rewritten by path mapping. Pass "tool" as "executable" instead.`, + `:58: "src.dirname" is not rewritten by path mapping. Add "src" to "ctx.actions.args()" with a "map_each" callback that returns "file.dirname" instead.`, + `:59: "ctx.bin_dir.path" is not rewritten by path mapping. Compute it from an output "File" in a "map_each" callback via "file.root.path" instead.`, + `:60: "ctx.genfiles_dir.path" is not rewritten by path mapping. Compute it from an output "File" in a "map_each" callback via "file.root.path" instead.`, + `:61: "src.root.path" is not rewritten by path mapping. Add "src" to "ctx.actions.args()" with a "map_each" callback that returns "file.root.path" instead.`, + `:62: "ctx.bin_dir.path" is not rewritten by path mapping. Compute it from an output "File" in a "map_each" callback via "file.root.path" instead.`, + `:63: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:63: "out.path" is not rewritten by path mapping. Pass "out" to "args.add()" instead, or use "args.add_all([out], expand_directories = False)" if it is a directory.`, + `:64: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:65: "src.path" is not rewritten by path mapping. Pass "src" to "args.add()" instead, or use "args.add_all([src], expand_directories = False)" if it is a directory.`, + `:66: "out_dir.path" is not rewritten by path mapping. Pass "out_dir" to "args.add()" instead, or use "args.add_all([out_dir], expand_directories = False)" if it is a directory.`, + `:67: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:68: "src.path" is not rewritten by path mapping. Pass "src" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:69: "inc.path" is not rewritten by path mapping. Pass "inc" to "args.add_all()" instead (with "expand_directories = False" if it is a directory).`, + `:72: "tool.path" is not rewritten by path mapping. Pass "tool" as "executable" instead.`, + `:73: "src.path" is not rewritten by path mapping. Add "src" to "ctx.actions.args()" and pass the "Args" object in "arguments" instead.`, + `:73: "out.path" is not rewritten by path mapping. Add "out" to "ctx.actions.args()" and pass the "Args" object in "arguments" instead.`, + `:77: "src.path" is not rewritten by path mapping. Add "src" to "ctx.actions.args()", pass the "Args" object in "arguments" and refer to it as "$1", "$2", ... in "command" instead.`, + `:77: "out.path" is not rewritten by path mapping. Add "out" to "ctx.actions.args()", pass the "Args" object in "arguments" and refer to it as "$1", "$2", ... in "command" instead.`, + `:81: "out.dirname" is not rewritten by path mapping. Add "out" to "ctx.actions.args()" with a "map_each" callback that returns "file.dirname" instead.`, + `:82: "src.path" is not rewritten by path mapping. Add "src" to "ctx.actions.args()" and pass the "Args" object in "arguments" instead.`, }, scopeBzl) }