2
0
Fork 0
mirror of https://github.com/bazel-contrib/bazel-lib synced 2024-11-27 17:43:27 +00:00
bazel-lib/lib/private/expand_make_vars.bzl
2022-02-11 07:52:01 -08:00

219 lines
9.3 KiB
Python

"Helpers to expand make variables"
# Convert an runfiles rootpath to a runfiles manifestpath.
# Runfiles rootpath is returned from ctx.expand_location $(rootpath) and $(rootpaths):
# - ./file
# - path/to/file
# - ../external_repo/path/to/file
# This is converted to the runfiles manifest path of:
# - repo/path/to/file
def _rootpath_to_runfiles_manifest_path(ctx, path):
if path.startswith("../"):
return path[len("../"):]
if path.startswith("./"):
path = path[len("./"):]
return ctx.workspace_name + "/" + path
# Expand $(rootpath) and $(rootpaths) to runfiles manifest path.
# Runfiles manifest path is of the form:
# - repo/path/to/file
def _expand_rootpath_to_manifest_path(ctx, input, targets):
paths = ctx.expand_location(input, targets)
return " ".join([_rootpath_to_runfiles_manifest_path(ctx, p) for p in paths.split(" ")])
def expand_locations(ctx, input, targets = []):
"""Expand location templates.
Expands all `$(execpath ...)`, `$(rootpath ...)` and legacy `$(location ...)` templates in the
given string by replacing with the expanded path. Expansion only works for labels that point to direct dependencies
of this rule or that are explicitly listed in the optional argument targets.
See https://docs.bazel.build/versions/main/be/make-variables.html#predefined_label_variables.
Use `$(rootpath)` and `$(rootpaths)` to expand labels to the runfiles path that a built binary can use
to find its dependencies. This path is of the format:
- `./file`
- `path/to/file`
- `../external_repo/path/to/file`
Use `$(execpath)` and `$(execpaths)` to expand labels to the execroot (where Bazel runs build actions).
This is of the format:
- `./file`
- `path/to/file`
- `external/external_repo/path/to/file`
- `<bin_dir>/path/to/file`
- `<bin_dir>/external/external_repo/path/to/file`
The legacy `$(location)` and `$(locations)` expansions are deprecated as they return the runfiles manifest path of the
format `repo/path/to/file` which behave differently than the built-in `$(location)` expansion in args of *_binary
and *_test rules which returns the rootpath.
See https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes-binaries.
The legacy `$(location)` and `$(locations)` expansion also differs from how the builtin `ctx.expand_location()` expansions
of `$(location)` and `$(locations)` behave as that function returns either the execpath or rootpath depending on the context.
See https://docs.bazel.build/versions/main/be/make-variables.html#predefined_label_variables.
The behavior of `$(location)` and `$(locations)` expansion will be fixed in a future major release to match the
to default Bazel behavior and return the same path as `ctx.expand_location()` returns for these.
The recommended approach is to now use `$(rootpath)` where you previously used $(location). See the docstrings
of `nodejs_binary` or `params_file` for examples of how to use `$(rootpath)` in `templated_args` and `args` respectively.
Args:
ctx: context
input: String to be expanded
targets: List of targets for additional lookup information.
Returns:
The expanded path or the original path
"""
target = "@%s//%s:%s" % (ctx.workspace_name, "/".join(ctx.build_file_path.split("/")[:-1]), ctx.attr.name)
# Loop through input an expand all predefined source/output path variables
# See https://docs.bazel.build/versions/main/be/make-variables.html#predefined_label_variables.
path = ""
length = len(input)
last = 0
for i in range(length):
# Support legacy $(location) and $(locations) expansions which return the runfiles manifest path
# in the format `repo/path/to/file`. This expansion is DEPRECATED. See docstring above.
# TODO: Change location to behave the same as the built-in $(location) expansion for args of *_binary
# and *_test rules. This would be a BREAKING CHANGE.
if input[i:].startswith("$(location ") or input[i:].startswith("$(locations "):
j = input.find(")", i) + 1
if (j == 0):
fail("invalid \"%s\" expansion in string \"%s\" part of target %s" % (input[i:j], input, target))
path += input[last:i]
path += _expand_rootpath_to_manifest_path(ctx, "$(rootpath" + input[i + 10:j], targets)
last = j
i = j
# Expand $(execpath) $(execpaths) $(rootpath) $(rootpaths) with plain ctx.expand_location()
if input[i:].startswith("$(execpath ") or input[i:].startswith("$(execpaths ") or input[i:].startswith("$(rootpath ") or input[i:].startswith("$(rootpaths "):
j = input.find(")", i) + 1
if (j == 0):
fail("invalid \"%s\" expansion in string \"%s\" part of target %s" % (input[i:j], input, target))
path += input[last:i]
path += ctx.expand_location(input[i:j], targets)
last = j
i = j
path += input[last:]
return path
def expand_variables(ctx, s, outs = [], output_dir = False, attribute_name = "args"):
"""Expand make variables and substitute like genrule does.
This function is the same as ctx.expand_make_variables with the additional
genrule-like substitutions of:
- `$@`: The output file if it is a single file. Else triggers a build error.
- `$(@D)`: The output directory. If there is only one file name in outs,
this expands to the directory containing that file. If there are multiple files,
this instead expands to the package's root directory in the bin tree,
even if all generated files belong to the same subdirectory!
- `$(RULEDIR)`: The output directory of the rule, that is, the directory
corresponding to the name of the package containing the rule under the bin tree.
See https://docs.bazel.build/versions/main/be/general.html#genrule.cmd and
https://docs.bazel.build/versions/main/be/make-variables.html#predefined_genrule_variables
for more information of how these special variables are expanded.
Args:
ctx: starlark rule context
s: expression to expand
outs: declared outputs of the rule, for expanding references to outputs
output_dir: whether the rule is expected to output a directory (TreeArtifact)
attribute_name: name of the attribute containing the expression
Returns:
`s` with the variables expanded
"""
rule_dir = [f for f in [
ctx.bin_dir.path,
ctx.label.workspace_root,
ctx.label.package,
] if f]
additional_substitutions = {}
if output_dir:
if s.find("$@") != -1 or s.find("$(@)") != -1:
fail("$@ substitution may only be used with output_dir=False.")
# We'll write into a newly created directory named after the rule
output_dir = [f for f in [
ctx.bin_dir.path,
ctx.label.workspace_root,
ctx.label.package,
ctx.label.name,
] if f]
else:
if s.find("$@") != -1 or s.find("$(@)") != -1:
if len(outs) > 1:
fail("$@ substitution may only be used with a single out.")
if len(outs) == 1:
additional_substitutions["@"] = outs[0].path
output_dir = outs[0].dirname.split("/")
else:
output_dir = rule_dir[:]
# The list comprehension removes empty segments like if we are in the root package
additional_substitutions["@D"] = "/".join([o for o in output_dir if o])
additional_substitutions["RULEDIR"] = "/".join([o for o in rule_dir if o])
return ctx.expand_make_variables(attribute_name, s, additional_substitutions)
def _expand_template_impl(ctx):
template = ctx.file.template
substitutions = ctx.attr.substitutions
subs = dict({
k: expand_locations(ctx, v, ctx.attr.data)
for k, v in substitutions.items()
}, **ctx.var)
ctx.actions.expand_template(
template = template,
output = ctx.outputs.out,
substitutions = subs,
is_executable = ctx.attr.is_executable,
)
expand_template = struct(
doc = """Template expansion
This performs a simple search over the template file for the keys in substitutions,
and replaces them with the corresponding values.
Values may also use location templates as documented in [expand_locations](#expand_locations)
as well as [configuration variables] such as `$(BINDIR)`, `$(TARGET_CPU)`, and `$(COMPILATION_MODE)`.
[configuration variables]: https://docs.bazel.build/versions/main/skylark/lib/ctx.html#var
""",
implementation = _expand_template_impl,
attrs = {
"template": attr.label(
doc = "The template file to expand.",
mandatory = True,
allow_single_file = True,
),
"substitutions": attr.string_dict(
doc = "Mapping of strings to substitutions.",
mandatory = True,
),
"out": attr.output(
doc = "Where to write the expanded file.",
mandatory = True,
),
"is_executable": attr.bool(
doc = "Whether to mark the output file as executable.",
default = False,
mandatory = False,
),
"data": attr.label_list(
doc = "List of targets for additional lookup information.",
allow_files = True,
),
},
)