193 lines
7.7 KiB
Python
193 lines
7.7 KiB
Python
"""
|
|
Copyright 2023 The Android Open Source Project
|
|
|
|
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.
|
|
"""
|
|
|
|
load("@bazel_skylib//lib:paths.bzl", "paths")
|
|
|
|
def _remove_extension(p):
|
|
"""Removes the extension from the path `p`.
|
|
|
|
Leading periods on the basename are ignored, so
|
|
`_strip_extension(".bashrc")` returns `".bashrc"`.
|
|
|
|
Args:
|
|
p: The path to modify.
|
|
|
|
Returns:
|
|
The path with the extension removed.
|
|
"""
|
|
|
|
# paths.split_extension() does all of the work.
|
|
return paths.split_extension(p)[0]
|
|
|
|
# Expands an output path template for the given context and input file.
|
|
def _expand_out_path_template(ctx, src_file):
|
|
# Each src_file has a short_path that looks like:
|
|
#
|
|
# <source-package-path>/<source-package-rel-path>/<base.ext>
|
|
#
|
|
# For some expansions, we want to strip of the source package path, and
|
|
# only use the rest for output file path in the expansion.
|
|
#
|
|
# There is also an option during expansion to just use the <base> or
|
|
# <base.ext> portion of the input path.
|
|
#
|
|
# This means there can be collisions if input files are taken from
|
|
# `filegroups` defined in different packages, if they happen to use
|
|
# the same relative path for that package.
|
|
#
|
|
# These conflcits are left to the user of this `gensrcs` rule to resolve for
|
|
# their use case, as at least Bazel will raise an error when they occur.
|
|
|
|
# Try to obtain the path to the package that defines `src_file`. It may or
|
|
# may not be defined by the same package this `gensrcs` rule is in.
|
|
# The `owner` label `package` attribute value is the closest we can get
|
|
# to that path, but it may not be correct in all cases, such as if the
|
|
# source path is itself for a generated file, where the generated file is
|
|
# under a build artifact path, and not in the source tree.
|
|
pkg_dirname = paths.dirname(src_file.short_path)
|
|
rel_dirname = pkg_dirname
|
|
if (src_file.is_source and src_file.owner and
|
|
src_file.short_path.startswith(src_file.owner.package + "/")):
|
|
rel_dirname = paths.dirname(paths.relativize(
|
|
src_file.short_path,
|
|
src_file.owner.package,
|
|
))
|
|
|
|
base_inc_ext = src_file.basename
|
|
base_exc_ext = _remove_extension(base_inc_ext)
|
|
rel_path_base_inc_ext = paths.join(rel_dirname, base_inc_ext)
|
|
rel_path_base_exc_ext = paths.join(rel_dirname, base_exc_ext)
|
|
pkg_path_base_inc_ext = paths.join(pkg_dirname, base_inc_ext)
|
|
pkg_path_base_exc_ext = paths.join(pkg_dirname, base_exc_ext)
|
|
|
|
# Expand the output template
|
|
return ctx.attr.output \
|
|
.replace("$(SRC:PKG/PATH/BASE.EXT)", pkg_path_base_inc_ext) \
|
|
.replace("$(SRC:PKG/PATH/BASE)", pkg_path_base_exc_ext) \
|
|
.replace("$(SRC:PATH/BASE.EXT)", rel_path_base_inc_ext) \
|
|
.replace("$(SRC:PATH/BASE)", rel_path_base_exc_ext) \
|
|
.replace("$(SRC:BASE.EXT)", base_inc_ext) \
|
|
.replace("$(SRC:BASE)", base_exc_ext) \
|
|
.replace("$(SRC)", rel_path_base_inc_ext)
|
|
|
|
# A rule to generate files based on provided srcs and tools.
|
|
def _gensrcs_impl(ctx):
|
|
# The next two assignments can be created by using ctx.resolve_command.
|
|
# TODO: Switch to using ctx.resolve_command when it is out of
|
|
# experimental.
|
|
command = ctx.expand_location(ctx.attr.cmd)
|
|
tools = [
|
|
tool[DefaultInfo].files_to_run
|
|
for tool in ctx.attr.tools
|
|
]
|
|
|
|
# Expand the shell command by substituting $(RULEDIR), which will be
|
|
# the same for any source file.
|
|
command = command.replace(
|
|
"$(RULEDIR)",
|
|
paths.join(
|
|
ctx.var["GENDIR"],
|
|
ctx.label.package,
|
|
),
|
|
)
|
|
|
|
src_files = ctx.files.srcs
|
|
out_files = []
|
|
for src_file in src_files:
|
|
# Expand the output path template for this source file.
|
|
out_file_path = _expand_out_path_template(ctx, src_file)
|
|
|
|
# out_file is at output_file_path that is relative to
|
|
# <GENDIR>/<gensrc-package-dir>, hence, the fullpath to out_file is
|
|
# <GENDIR>/<gensrc-package-dir>/<out_file_path>
|
|
out_file = ctx.actions.declare_file(out_file_path)
|
|
|
|
# Expand the command template for this source file by performing
|
|
# substitution for $(SRC) and $(OUT).
|
|
shell_command = command \
|
|
.replace("$(SRC)", src_file.path) \
|
|
.replace("$(OUT)", out_file.path)
|
|
|
|
# Run the shell comand to generate the output from the input.
|
|
ctx.actions.run_shell(
|
|
tools = tools,
|
|
outputs = [out_file],
|
|
inputs = [src_file],
|
|
command = shell_command,
|
|
progress_message = "Generating %s from %s" % (
|
|
out_file.path,
|
|
src_file.path,
|
|
),
|
|
)
|
|
out_files.append(out_file)
|
|
|
|
return [DefaultInfo(
|
|
files = depset(out_files),
|
|
)]
|
|
|
|
gensrcs = rule(
|
|
implementation = _gensrcs_impl,
|
|
doc = "This rule generates files, where each of the `srcs` files is " +
|
|
"passed to `cmd` to generate an `output`.",
|
|
attrs = {
|
|
"srcs": attr.label_list(
|
|
# We allow srcs to directly reference files, instead of only
|
|
# allowing references to other rules such as filegroups.
|
|
allow_files = True,
|
|
# An empty srcs is likely an mistake.
|
|
allow_empty = False,
|
|
# srcs must be explicitly specified.
|
|
mandatory = True,
|
|
doc = "A list of source files to process",
|
|
),
|
|
"output": attr.string(
|
|
# By default we generate an output filename based on the input
|
|
# filename (no extension).
|
|
default = "$(SRC)",
|
|
doc = "An output path template which is expanded to generate " +
|
|
"the output path given an source file. Portions " +
|
|
"of the source filename can be included in the expansion " +
|
|
"with one of: $(SRC:BASE), $(SRC:BASE.EXT), " +
|
|
"$(SRC:PATH/BASE), $(SRC:PATH/BASE), " +
|
|
"$(SRC:PKG/PATH/BASE), or $(SRC:PKG/PATH/BASE.ext). For " +
|
|
"example, specifying `output = " +
|
|
"\"includes/lib/$(SRC:BASE).h\"` would mean the input " +
|
|
"file `some_path/to/a.txt` generates `includes/lib/a.h`, " +
|
|
"while instead specifying `output = " +
|
|
"\"includes/lib/$(SRC:PATH/BASE.EXT).h\"` would expand " +
|
|
"to `includes/lib/some_path/to/a.txt.h`.",
|
|
),
|
|
"cmd": attr.string(
|
|
# cmd must be explicitly specified.
|
|
mandatory = True,
|
|
doc = "The command to run. Subject to $(location) expansion. " +
|
|
"$(SRC) represents each input file provided in `srcs` " +
|
|
"while $(OUT) reprensents corresponding output file " +
|
|
"generated by the rule. $(RULEDIR) is intepreted the same " +
|
|
"as it is in genrule.",
|
|
),
|
|
"tools": attr.label_list(
|
|
# We allow tools to directly reference files, as there could be a local script
|
|
# used as a tool.
|
|
allow_files = True,
|
|
doc = "A list of tool dependencies for this rule. " +
|
|
"The path of an individual `tools` target //x:y can be " +
|
|
"obtained using `$(location //x:y)`",
|
|
cfg = "exec",
|
|
),
|
|
},
|
|
)
|