Delete maprule. Fix Buildifier lint errors. (#192)

* Delete maprule. Fix Buildifier lint errors.

Delete maprule and its tests: I wrote this rule,
and I no longer plan to release it. Alternative
rules exist that serve users' needs better.

Fix also Buildifier lint errors that were making
BuildKite red: https://buildkite.com/bazel/bazel-skylib/builds/659#ab98ac31-6e1c-415e-b8a8-5f8868340c7d
This commit is contained in:
László Csomor 2019-09-17 14:03:22 +02:00 committed by GitHub
parent 90ea6feaf3
commit 58068fe0cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 22 additions and 1287 deletions

View File

@ -28,3 +28,14 @@ bazel_skylib_internal_deps()
load("//:internal_setup.bzl", "bazel_skylib_internal_setup")
bazel_skylib_internal_setup()
http_archive(
name = "rules_cc",
sha256 = "b4b2a2078bdb7b8328d843e8de07d7c13c80e6c89e86a09d6c4b424cfd1aaa19",
strip_prefix = "rules_cc-cb2dfba6746bfa3c3705185981f3109f0ae1b893",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_cc/archive/cb2dfba6746bfa3c3705185981f3109f0ae1b893.zip",
"https://github.com/bazelbuild/rules_cc/archive/cb2dfba6746bfa3c3705185981f3109f0ae1b893.zip",
],
)

View File

@ -133,6 +133,6 @@ stardoc(
name = "common_settings_docs",
out = "common_settings_doc_gen.md",
input = "//rules:common_settings.bzl",
deps = ["//rules:common_settings"],
semantic_flags = ["--experimental_build_setting_api=True"],
deps = ["//rules:common_settings"],
)

View File

@ -29,18 +29,19 @@ def _diff_test_impl(ctx):
test_bin = ctx.actions.declare_file(ctx.label.name + "-test.bat")
ctx.actions.write(
output = test_bin,
content = r"""@echo off
content = """@rem Generated by diff_test.bzl, do not edit.
@echo off
SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION
set MF=%RUNFILES_MANIFEST_FILE:/=\%
set PATH=%SYSTEMROOT%\system32
set MF=%RUNFILES_MANIFEST_FILE:/=\\%
set PATH=%SYSTEMROOT%\\system32
set F1={file1}
set F2={file2}
if "!F1:~0,9!" equ "external/" (set F1=!F1:~9!) else (set F1=!TEST_WORKSPACE!/!F1!)
if "!F2:~0,9!" equ "external/" (set F2=!F2:~9!) else (set F2=!TEST_WORKSPACE!/!F2!)
for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F1! " "%MF%"`) do (
set RF1=%%i
set RF1=!RF1:/=\!
set RF1=!RF1:/=\\!
)
if "!RF1!" equ "" (
echo>&2 ERROR: !F1! not found
@ -48,7 +49,7 @@ if "!RF1!" equ "" (
)
for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F2! " "%MF%"`) do (
set RF2=%%i
set RF2=!RF2:/=\!
set RF2=!RF2:/=\\!
)
if "!RF2!" equ "" (
echo>&2 ERROR: !F2! not found

View File

@ -14,23 +14,6 @@ bzl_library(
visibility = ["//rules:__pkg__"],
)
bzl_library(
name = "maprule",
srcs = ["maprule.bzl"],
visibility = ["//tests:__pkg__"],
deps = [":maprule_private"],
)
bzl_library(
name = "maprule_private",
srcs = ["maprule_private.bzl"],
deps = [
":maprule_util",
"//lib:dicts",
"//lib:paths",
],
)
bzl_library(
name = "maprule_util",
srcs = ["maprule_util.bzl"],

View File

@ -1,28 +0,0 @@
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# 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.
"""Maprule implementation in Starlark.
This module exports the cmd_maprule() and bash_maprule() build rules.
They are the same except for the interpreter they use (cmd.exe and Bash respectively) and for the
expected language of their `cmd` attribute.
You can read more about these rules in "maprule_private.bzl".
"""
load(":maprule_private.bzl", _bash_maprule = "bash_maprule", _cmd_maprule = "cmd_maprule")
cmd_maprule = _cmd_maprule
bash_maprule = _bash_maprule

View File

@ -1,574 +0,0 @@
# Copyright 2018 The Bazel Authors. All rights reserved.
#
# 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.
"""Maprule implementation in Starlark.
This module exports:
- The cmd_maprule() and bash_maprule() build rules.
They are the same except for the interpreter they use (cmd.exe and Bash respectively) and for the
expected language of their `cmd` attribute. We will refer to them collectively as `maprule`.
- The maprule_testing struct. This should only be used by maprule's own unittests.
"""
load("//lib:dicts.bzl", "dicts")
load("//lib:paths.bzl", "paths")
load(
":maprule_util.bzl",
"BASH_STRATEGY",
"CMD_STRATEGY",
"fail_if_errors",
"resolve_locations",
)
_cmd_maprule_intro = """
Maprule that runs a Windows Command Prompt (`cmd.exe`) command.
This rule is the same as `bash_maprule`, but uses `cmd.exe` instead of Bash, therefore the `cmd`
attribute must use Command Prompt syntax. `cmd_maprule` rules can only be built on Windows.
"""
_bash_maprule_intro = """
Maprule that runs a Bash command.
This rule is the same as `cmd_maprule` except this one uses Bash to run the command, therefore the
`cmd` attribute must use Bash syntax. `bash_maprule` rules can only be built if Bash is installed on
the build machine.
"""
_cmd_maprule_example = """
# This file is //projects/game:BUILD
load("@bazel_skylib//rules:maprule.bzl", "cmd_maprule")
cmd_maprule(
name = "process_assets",
foreach_srcs = [
"rust.png",
"teapot.3ds",
"//assets:wood.jpg",
"//assets:models",
],
outs_templates = {
"TAGS": "{src}.tags",
"DIGEST": "digests/{src_name_noext}.md5",
},
tools = [
"//bin:asset-tagger",
"//util:md5sum",
],
add_env = {
"ASSET_TAGGER": "$(location //bin:asset-tagger)",
"MD5SUM": "$(location //util:md5sum)",
},
# Note: this command should live in a script file, we only inline it in the `cmd` attribute
# for the sake of demonstration. See Tips and Tricks section.
cmd = "%MAPRULE_ASSET_TAGGER% --input=%MAPRULE_SRC% --output=%MAPRULE_TAGS% & " +
'IF /I "%ERRORLEVEL%" EQU "0" ( %MAPRULE_MD5SUM% %MAPRULE_SRC% > %MAPRULE_DIGEST% )',
)
"""
_bash_maprule_example = """
# This file is //projects/game:BUILD
load("@bazel_skylib//rules:maprule.bzl", "bash_maprule")
bash_maprule(
name = "process_assets",
foreach_srcs = [
"rust.png",
"teapot.3ds",
"//assets:wood.jpg",
"//assets:models",
],
outs_templates = {
"TAGS": "{src}.tags",
"DIGEST": "digests/{src_name_noext}.md5",
},
tools = [
"//bin:asset-tagger",
"//util:md5sum",
],
add_env = {
"ASSET_TAGGER": "$(location //bin:asset-tagger)",
"MD5SUM": "$(location //util:md5sum)",
},
# Note: this command should live in a script file, we only inline it in the `cmd` attribute
# for the sake of demonstration. See Tips and Tricks section.
cmd = '"$MAPRULE_ASSET_TAGGER" --input="$MAPRULE_SRC" --output="$MAPRULE_TAGS" && ' +
'"$MAPRULE_MD5SUM" "$MAPRULE_SRC" > "$MAPRULE_DIGEST"',
)
"""
_rule_doc_template = """
{intro}
Maprule runs a specific command for each of the "foreach" source files. This allows processing
source files in parallel, to produce some outputs for each of them.
The name "maprule" indicates that this rule can be used to map source files to output files, and is
also a reference to "genrule" that inspired this rule's design (though there are significant
differences).
Below you will find an Example, Tips and Tricks, and an FAQ.
### Example
{example}
The "process_assets" rule above will execute the command in the `cmd` to process "rust.png",
"teapot.3ds", "//assets:wood.jpg", and every file in the "//assets:models" rule, producing the
corresponding .tags and .md5 files for each of them, under the following paths:
bazel-bin/projects/game/process_assets_out/projects/game/rust.png.tags
bazel-bin/projects/game/process_assets_out/digests/rust.md5
bazel-bin/projects/game/process_assets_out/projects/game/teapot.3ds.tags
bazel-bin/projects/game/process_assets_out/digests/teapot.md5
bazel-bin/projects/game/process_assets_out/assets/wood.jpg.tags
bazel-bin/projects/game/process_assets_out/digests/wood.md5
...
You can create downstream rules, for example a filegroup or genrule (or another maprule) that put
this rule in their `srcs` attribute and get all the .tags and .md5 files.
## Tips and Tricks
*(The Tips and Tricks section is the same for `cmd_maprule` and `bash_maprule`.)*
### Use script files instead of inlining commands in the `cmd` attribute.
Unless the command is trivial, don't try to write it in `cmd`.
Properly quoting parts of the command is challenging enough, add to that escaping for the BUILD
file's syntax and the `cmd` attribute quickly gets unmaintainably complex.
It's a lot easier and maintainable to create a script file such as "foo.sh" in `bash_maprule` or
"foo.bat" in `cmd_maprule` for the commands. To do that:
1. move the commands to a script file
2. add the script file to the `tools` attribute
3. add an entry to the `add_env` attribute, e.g. "`TOOL: "$(location :tool.sh)"`"
4. replace the `cmd` with just "$MAPRULE_FOO" (in `bash_maprule`) or "%MAPRULE_FOO%" (in
`cmd_maprule`).
Doing this also avoids hitting command line length limits.
Example:
cmd_maprule(
...
srcs = ["//data:temperatures.txt"],
tools = [
"command.bat",
":weather-stats",
],
add_env = {{
"COMMAND": "$(location :command.bat)",
"STATS_TOOL": "$(location :weather-stats-computer)",
"TEMPERATURES": "$(location //data:temperatures.txt)",
}},
cmd = "%MAPRULE_COMMAND%",
)
### Use the `add_env` attribute to pass tool locations to the command.
Entries in the `add_env` attribute may use "$(location)" references and may also use the same
placeholders as the `outs_templates` attribute. For example you can use this mechanism to pass extra
"$(location)" references of `tools` or `srcs` to the actions.
Example:
cmd_maprule(
...
foreach_srcs = [...],
outs_templates = {{"STATS_OUT": "{{src}}-stats.txt"}},
srcs = ["//data:temperatures.txt"],
tools = [":weather-stats"],
add_env = {{
"STATS_TOOL": "$(location :weather-stats-computer)",
"TEMPERATURES": "$(location //data:temperatures.txt)",
}},
cmd = "%MAPRULE_STATS_TOOL% --src=%MAPRULE_SRC% --data=%MAPRULE_TEMPERATURES% > %MAPRULE_STATS_OUT%",
)
cc_binary(
name = "weather-stats",
...
)
## Environment Variables
*(The Environment Variables section is the same for `cmd_maprule` and `bash_maprule`.)*
The rule defines several environment variables available to the command may reference. All of these
envvars names start with "MAPRULE_". You can add your own envvars with the `add_env` attribute.
The command can use some envvars, all named "MAPRULE_*<something>*".
The complete list of environment variables is:
- "MAPRULE_SRC": the path of the current file from `foreach_srcs`
- "MAPRULE_SRCS": the space-separated paths of all files in the `srcs` attribute
- "MAPRULE_*<OUT>*": for each key name *<OUT>* in the `outs_templates` attribute, this
is the path of the respective output file for the current source
- "MAPRULE_*<ENV>*": for each key name *<ENV>* in the `add_env` attribute
## FAQ
*(The FAQ section is the same for `cmd_maprule` and `bash_maprule`.)*
### What's the motivation for maprule? What's wrong with genrule?
genrule creates a single action for all of its inputs. It requires specifying all output files.
Finally, it can only create Bash commands.
Maprule supports parallel processing of its inputs and doesn't require specifying all outputs, just
templates for the outputs. And not only Bash is supported (via `bash_maprule`) but so are
cmd.exe-style commands via `cmd_maprule`.
### `genrule.cmd` supports "$(location)" expressions, how come `*_maprule.cmd` doesn't?
Maprule deliberately omits support for this feature to avoid pitfalls with quoting and escaping, and
potential issues with paths containing spaces. Instead, maprule exports environment variables for
the input and outputs of the action, and allows the user to define extra envvars. These extra
envvars do support "$(location)" expressions, so you can pass paths of labels in `srcs` and `tools`.
### Why are all envvars exported with the "MAPRULE_" prefix?
To avoid conflicts with other envvars, whose names could also be attractive outs_templates names.
### Why do `outs_templates` and `add_env` keys have to be uppercase?
Because they are exported as all-uppercase envvars, so requiring that they be declared as uppercase
gives a visual cue of that. It also avoids clashes resulting from mixed lower-upper-case names like
"foo" and "Foo".
### Why don't `outs_templates` and `add_env` keys have to start with "MAPRULE_" even though they are exported as such?
For convenience. It seems to bring no benefit to have the user always type "MAPRULE_" in front of
the name when the rule itself could as well add it.
### Why are all outputs relative to "*<maprule_pkg>*/*<maprule_name>*_out/" ?
To ensure that maprules in the same package and with the same outs_templates produce distinct output
files.
### Why aren't `outs_templates` keys available as placeholders in the values of `add_env`?
Because `add_env` is meant to be used for passing extra "$(location)" information to the action, and
the output paths are already available as envvars for the action.
"""
def _is_relative_path(p):
"""Returns True if `p` is a relative path (considering Unix and Windows semantics)."""
return p and p[0] != "/" and p[0] != "\\" and (
len(p) < 2 or not p[0].isalpha() or p[1] != ":"
)
def _validate_attributes(ctx_attr_outs_templates, ctx_attr_add_env):
"""Validates rule attributes and returns a list of error messages if there were errors."""
errors = []
envvars = {
"MAPRULE_SRC": "the source file",
"MAPRULE_SRCS": "the space-joined paths of the common sources",
}
if not ctx_attr_outs_templates:
errors.append("ERROR: \"outs_templates\" must not be empty")
names_to_paths = {}
paths_to_names = {}
# Check entries in "outs_templates".
for name, path in ctx_attr_outs_templates.items():
# Check the entry's name.
envvar_for_name = "MAPRULE_" + name.upper()
error_prefix = "ERROR: In \"outs_templates\" entry {\"%s\": \"%s\"}: " % (name, path)
if not name:
errors.append("ERROR: Bad entry in the \"outs_templates\" attribute: the name " +
"should not be empty.")
elif name.upper() != name:
errors.append(error_prefix + "the name should be all upper-case.")
elif envvar_for_name in envvars:
errors.append((error_prefix +
"please rename it, otherwise this output path would be exported " +
"as the environment variable \"%s\", conflicting with the " +
"environment variable of %s.") % (
envvar_for_name,
envvars[envvar_for_name],
))
elif not path:
errors.append(error_prefix + "output path should not be empty.")
elif not _is_relative_path(path):
errors.append(error_prefix + "output path should be relative.")
elif ".." in path:
errors.append(error_prefix + "output path should not contain uplevel references.")
elif path in paths_to_names:
errors.append(error_prefix +
"output path is already used for \"%s\"." % paths_to_names[path])
envvars[envvar_for_name] = "the \"%s\" output file declared in the \"outs_templates\" attribute" % name
names_to_paths[name] = path
paths_to_names[path] = name
# Check envvar names in "add_env".
for name, value in ctx_attr_add_env.items():
envvar_for_name = "MAPRULE_" + name.upper()
error_prefix = "ERROR: In \"add_env\" entry {\"%s\": \"%s\"}: " % (name, value)
if not name:
errors.append("ERROR: Bad entry in the \"add_env\" attribute: the name should not be " +
"empty.")
elif name.upper() != name:
errors.append(error_prefix + "the name should be all upper-case.")
elif envvar_for_name in envvars:
errors.append((error_prefix +
"please rename it, otherwise it would be exported as \"%s\", " +
"conflicting with the environment variable of %s.") % (
envvar_for_name,
envvars[envvar_for_name],
))
elif "$(location" in value:
tokens = value.split("$(location")
if len(tokens) != 2:
errors.append(error_prefix + "use only one $(location) or $(locations) function.")
elif ")" not in tokens[1]:
errors.append(error_prefix +
"incomplete $(location) or $(locations) function, missing closing " +
"parenthesis")
envvars[name] = "an additional environment declared in \"add_env\" as \"%s\"" % name
return errors
def _src_placeholders(src, strategy):
return {
"src": strategy.as_path(src.short_path),
"src_dir": strategy.as_path(paths.dirname(src.short_path) + "/"),
"src_name": src.basename,
"src_name_noext": (src.basename[:-len(src.extension) - 1] if len(src.extension) else src.basename),
}
def _create_outputs(ctx, ctx_label_name, ctx_attr_outs_templates, strategy, foreach_srcs):
errors = []
outs_dicts = {}
output_generated_by = {}
all_output_files = []
src_placeholders_dicts = {}
output_path_prefix = ctx_label_name + "_out/"
for src in foreach_srcs:
src_placeholders_dicts[src] = _src_placeholders(src, strategy)
outputs_for_src = {}
for template_name, path in ctx_attr_outs_templates.items():
output_path = path.format(**src_placeholders_dicts[src])
if output_path in output_generated_by:
existing_generator = output_generated_by[output_path]
errors.append("\n".join([
"ERROR: output file generated multiple times:",
" output file: " + output_path,
" foreach_srcs file 1: " + existing_generator[0].short_path,
" outs_templates entry 1: " + existing_generator[1],
" foreach_srcs file 2: " + src.short_path,
" outs_templates entry 2: " + template_name,
]))
output_generated_by[output_path] = (src, template_name)
output = ctx.actions.declare_file(output_path_prefix + output_path)
outputs_for_src[template_name] = output
all_output_files.append(output)
outs_dicts[src] = outputs_for_src
if errors:
# For sake of Starlark unittest we return all_output_files, so the test can create dummy
# generating actions for the files even in case of errors.
return None, all_output_files, None, errors
else:
return outs_dicts, all_output_files, src_placeholders_dicts, None
def _custom_envmap(ctx, strategy, src_placeholders, outs_dict, resolved_add_env):
return dicts.add(
{
"MAPRULE_" + k.upper(): strategy.as_path(v)
for k, v in src_placeholders.items()
},
{
"MAPRULE_" + k.upper(): strategy.as_path(v.path)
for k, v in outs_dict.items()
},
{
"MAPRULE_" + k.upper(): v
for k, v in resolved_add_env.items()
},
)
def _maprule_main(ctx, strategy):
errors = _validate_attributes(ctx.attr.outs_templates, ctx.attr.add_env)
fail_if_errors(errors)
# From "srcs": merge the depsets in the DefaultInfo.files of the targets.
common_srcs = depset(transitive = [t[DefaultInfo].files for t in ctx.attr.srcs])
common_srcs_list = common_srcs.to_list()
# From "foreach_srcs": by accessing the attribute's value through ctx.files (a list, not a
# depset), we flatten the depsets of DefaultInfo.files of the targets and merge them to a single
# list. This is fine, we would have to do this anyway, because we iterate over them later.
foreach_srcs = ctx.files.foreach_srcs
# Create the outputs for each file in "foreach_srcs".
foreach_src_outs_dicts, all_outputs, src_placeholders_dicts, errors = _create_outputs(
ctx,
ctx.label.name,
ctx.attr.outs_templates,
strategy,
foreach_srcs,
)
fail_if_errors(errors)
progress_message = (ctx.attr.message or "Executing maprule") + " for %s" % ctx.label
# Create the part of the environment variables map that all actions will share.
common_envmap = dicts.add(
ctx.configuration.default_shell_env,
{"MAPRULE_SRCS": " ".join([strategy.as_path(p.path) for p in common_srcs_list])},
)
# Resolve "tools" runfiles and $(location) references in "add_env".
inputs_from_tools, manifests_from_tools = ctx.resolve_tools(tools = ctx.attr.tools)
add_env = resolve_locations(ctx, strategy, ctx.attr.add_env)
# Create actions for each of the "foreach" sources.
for src in foreach_srcs:
strategy.create_action(
ctx,
inputs = depset(direct = [src], transitive = [common_srcs, inputs_from_tools]),
outputs = foreach_src_outs_dicts[src].values(),
# The custom envmap contains envvars specific to the current "src", such as MAPRULE_SRC.
env = dicts.add(
common_envmap,
_custom_envmap(
ctx,
strategy,
src_placeholders_dicts[src],
foreach_src_outs_dicts[src],
add_env,
),
),
command = ctx.attr.cmd,
progress_message = progress_message,
mnemonic = "Maprule",
manifests_from_tools = manifests_from_tools,
)
return [DefaultInfo(files = depset(all_outputs))]
def _cmd_maprule_impl(ctx):
return _maprule_main(ctx, CMD_STRATEGY)
def _bash_maprule_impl(ctx):
return _maprule_main(ctx, BASH_STRATEGY)
_ATTRS = {
"srcs": attr.label_list(
allow_files = True,
doc = "The set of source files common to all actions of this rule.",
),
"add_env": attr.string_dict(
doc = "Extra environment variables to define for the actions. Every variable's name " +
"must be uppercase. Bazel will automatically prepend \"MAPRULE_\" to the name " +
"when exporting the variable for the action. The values may use \"$(location)\" " +
"expressions for labels declared in the `srcs` and `tools` attribute, and " +
"may reference the same placeholders as the values of the `outs_templates` " +
"attribute.",
),
"cmd": attr.string(
mandatory = True,
doc = "The command to execute. It must be in the syntax corresponding to this maprule " +
"type, e.g. for `bash_maprule` this must be a Bash command, and for `cmd_maprule` " +
"a Windows Command Prompt (cmd.exe) command. Several environment variables are " +
"available for this command, storing values like the paths of the input and output " +
"files of the action. See the \"Environment Variables\" section for the complete " +
"list of environment variables available to this command.",
),
"foreach_srcs": attr.label_list(
allow_files = True,
mandatory = True,
doc = "The set of sources that will be processed one by one in parallel, to produce " +
"the templated outputs. Each of these source files will will be processed " +
"individually by its own action.",
),
"message": attr.string(
doc = "A custom progress message to display as the actions are executed.",
),
"outs_templates": attr.string_dict(
allow_empty = False,
mandatory = True,
doc = "<p>Templates for the output files. Each key defines a name for an output file " +
"and the value specifies a path template for that output. For each of the " +
"files in `foreach_srcs` this rule creates one action that produces all of " +
"these outputs. The paths of the particular output files for that input are " +
"computed from the template. The ensure the resolved templates yield unique " +
"paths, the following placeholders are supported in the path " +
"templates:</p>" +
"<ul>" +
"<li>\"{src}\": same as \"{src_dir}/{src_name}\"</li>" +
"<li>\"{src_dir}\": package path of the source file, and a trailing \"/\"</li>" +
"<li>\"{src_name}\": base name of the source file</li>" +
"<li>\"{src_name_noext}\": same as \"{src_name}\" without the file extension</li>" +
"</ul>" +
"<p>You may also add extra path components to the templates, as long as the path " +
"template is relative and does not contain uplevel references (\"..\"). " +
"Placeholders will be replaced by the values corresponding to the respective " +
"input file in `foreach_srcs`. Every output file is generated under " +
"&lt;bazel_bin&gt;/path/to/maprule/&lt;maprule_name&gt; + \"_outs/\".</p>",
),
"tools": attr.label_list(
cfg = "host",
allow_files = True,
doc = "Tools used by the command. The `cmd` attribute, and the values of the " +
"`add_env` attribute may reference these tools in \"$(location)\" expressions, " +
"similar to the genrule rule.",
),
}
# Maprule that uses Windows cmd.exe as the interpreter.
cmd_maprule = rule(
implementation = _cmd_maprule_impl,
doc = _rule_doc_template.format(
intro = _cmd_maprule_intro,
example = _cmd_maprule_example,
),
attrs = _ATTRS,
)
# Maprule that uses Bash as the interpreter.
bash_maprule = rule(
implementation = _bash_maprule_impl,
doc = _rule_doc_template.format(
intro = _bash_maprule_intro,
example = _bash_maprule_example,
),
attrs = _ATTRS,
)
# Only used in unittesting maprule.
maprule_testing = struct(
cmd_strategy = CMD_STRATEGY,
bash_strategy = BASH_STRATEGY,
src_placeholders = _src_placeholders,
validate_attributes = _validate_attributes,
is_relative_path = _is_relative_path,
custom_envmap = _custom_envmap,
create_outputs = _create_outputs,
)

View File

@ -1,24 +0,0 @@
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# 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.
"""Maprule implementation in Starlark.
This module exports:
This module exports the maprule_testing struct. This should only be used by maprule's own unittests.
"""
load(":maprule_private.bzl", _maprule_testing = "maprule_testing")
maprule_testing = _maprule_testing

View File

@ -2,7 +2,6 @@ load("//:bzl_library.bzl", "bzl_library")
load(":build_test_tests.bzl", "build_test_test_suite")
load(":collections_tests.bzl", "collections_test_suite")
load(":dicts_tests.bzl", "dicts_test_suite")
load(":maprule_tests.bzl", "maprule_test_suite")
load(":old_sets_tests.bzl", "old_sets_test_suite")
load(":new_sets_tests.bzl", "new_sets_test_suite")
load(":partial_tests.bzl", "partial_test_suite")
@ -27,8 +26,6 @@ collections_test_suite()
dicts_test_suite()
maprule_test_suite()
old_sets_test_suite()
new_sets_test_suite()

View File

@ -15,6 +15,7 @@
"""Unit tests for build_test.bzl."""
load("//rules:build_test.bzl", "build_test")
load("@rules_cc//cc:defs.bzl", "cc_library")
def build_test_test_suite():
# Since the rules doesn't do anything really, it just makes some targets
@ -29,7 +30,7 @@ def build_test_test_suite():
)
# Use it in a non-test target
native.cc_library(
cc_library(
name = "build_test__build_target",
srcs = [":build_test__make_src"],
)

View File

@ -1,52 +0,0 @@
# This package aids testing the 'maprule' rule.
#
# ATTENTION: As of 2019-03-20, maprule is not ready for public use.
# @laszlocsomor is planning incompatible changes to the rule.
load("//rules/private:maprule.bzl", "bash_maprule")
licenses(["notice"])
package(default_testonly = 1)
sh_test(
name = "maprule_tests",
srcs = ["maprule_tests.sh"],
data = [
# Use DefaultInfo.files from 'mr_bash' (via 'file_deps').
":file_deps",
"//tests:unittest.bash",
],
deps = ["@bazel_tools//tools/bash/runfiles"],
)
filegroup(
name = "file_deps",
# Use DefaultInfo.files from 'mr_bash'.
srcs = [":mr_bash"],
)
bash_maprule(
name = "mr_bash",
srcs = ["common.txt"],
add_env = {
"TOOL": "$(location :mr_bash_tool)",
},
# TODO(laszlocsomor): add quotes around $MAPRULE_TOOL after
# https://github.com/bazelbuild/bazel/issues/7454 is fixed.
cmd = "$MAPRULE_TOOL",
foreach_srcs = [
"foo.txt",
"b/bar.txt",
],
outs_templates = {
"OUT1": "{src}.out1",
"OUT2": "out2/{src_name_noext}",
},
tools = [":mr_bash_tool"],
)
sh_binary(
name = "mr_bash_tool",
srcs = ["mr_bash_tool.sh"],
)

View File

@ -1 +0,0 @@
bar file

View File

@ -1 +0,0 @@
common file

View File

@ -1 +0,0 @@
foo file

View File

@ -1,61 +0,0 @@
#!/bin/bash
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# 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.
# --- begin runfiles.bash initialization ---
set -euo pipefail
if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
if [[ -f "$0.runfiles_manifest" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
elif [[ -f "$0.runfiles/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
export RUNFILES_DIR="$0.runfiles"
fi
fi
if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
"$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
else
echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
exit 1
fi
# --- end runfiles.bash initialization ---
source "$(rlocation bazel_skylib/tests/unittest.bash)" \
|| { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
function test_bash_maprule() {
local -r out1foo="$(rlocation bazel_skylib/tests/maprule/mr_bash_out/tests/maprule/foo.txt.out1)"
local -r out1bar="$(rlocation bazel_skylib/tests/maprule/mr_bash_out/tests/maprule/b/bar.txt.out1)"
local -r out2foo="$(rlocation bazel_skylib/tests/maprule/mr_bash_out/out2/foo)"
local -r out2bar="$(rlocation bazel_skylib/tests/maprule/mr_bash_out/out2/bar)"
cat "$out1foo" | tr '\n\r' ' ' > "$TEST_log"
expect_log "^tests/maprule/common.txt *tests/maprule/foo.txt *$"
cat "$out1bar" | tr '\n\r' ' ' > "$TEST_log"
expect_log "^tests/maprule/common.txt *tests/maprule/b/bar.txt *$"
cat "$out2foo" | tr '\n\r' ' ' > "$TEST_log"
expect_log "^common file *foo file *$"
cat "$out2bar" | tr '\n\r' ' ' > "$TEST_log"
expect_log "^common file *bar file *$"
}
run_suite "maprule test suite"

View File

@ -1,20 +0,0 @@
#!/bin/bash
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# 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.
set -euo pipefail
echo "$MAPRULE_SRCS" "$MAPRULE_SRC" > "$MAPRULE_OUT1"
cat "$MAPRULE_SRCS" "$MAPRULE_SRC" > "$MAPRULE_OUT2"

View File

@ -1,498 +0,0 @@
# Copyright 2018 The Bazel Authors. All rights reserved.
#
# 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.
"""Unit tests for maprule.bzl."""
load("//lib:unittest.bzl", "asserts", "unittest")
load("//rules/private:maprule_testing.bzl", "maprule_testing")
def _dummy_generating_action(ctx, path):
ctx.actions.write(path, "hello")
def _mock_file(ctx, path):
f = ctx.actions.declare_file(path)
_dummy_generating_action(ctx, f)
return f
def _lstrip_until(s, until):
return s[s.find(until):]
def _assert_dict_keys(env, expected, actual, msg):
asserts.equals(env, {k: None for k in expected}, {k: None for k in actual}, msg)
def _assert_ends_with(env, expected_ending, s, msg):
if not s.endswith(expected_ending):
unittest.fail(env, msg + ": expected \"%s\" to end with \"%s\"" % (s, expected_ending))
def _assert_no_error(env, errors, msg):
if errors:
unittest.fail(env, msg + ": expected no errors, got: [%s]" % "\n".join(errors))
def _assert_error(env, errors, expected_fragment, msg):
for e in errors:
if expected_fragment in e:
return
unittest.fail(env, msg + ": did not find \"%s\" in: [%s]" % (expected_fragment, "\n".join(errors)))
def _contains_substrings_in_order(s, substrings):
index = 0
for ss in substrings:
index = s.find(ss, index)
if index < 0:
return False
index += len(ss)
return True
def _assert_error_fragments(env, errors, expected_fragments, msg):
for e in errors:
if _contains_substrings_in_order(e, expected_fragments):
return
unittest.fail(env, msg + ": did not find expected fragments in \"%s\" in order" % "\n".join(errors))
def _src_placeholders_test(ctx):
env = unittest.begin(ctx)
for language, strategy in [
("cmd", maprule_testing.cmd_strategy),
("bash", maprule_testing.bash_strategy),
]:
for basename, basename_noext in [("bar.txt", "bar"), ("bar.pb.h", "bar.pb")]:
actual = maprule_testing.src_placeholders(
_mock_file(ctx, language + "/foo/" + basename),
strategy,
)
_assert_dict_keys(
env,
["src", "src_dir", "src_name", "src_name_noext"],
actual,
"assertion #1 (language: %s, basename: %s)" % (language, basename),
)
_assert_ends_with(
env,
strategy.as_path(language + "/foo/" + basename),
actual["src"],
"assertion #2 (language: %s, basename: %s)" % (language, basename),
)
_assert_ends_with(
env,
strategy.as_path(language + "/foo/"),
actual["src_dir"],
"assertion #3 (language: %s, basename: %s)" % (language, basename),
)
asserts.equals(
env,
basename,
actual["src_name"],
"assertion #4 (language: %s, basename: %s)" % (language, basename),
)
asserts.equals(
env,
basename_noext,
actual["src_name_noext"],
"assertion #5 (language: %s, basename: %s)" % (language, basename),
)
return unittest.end(env)
src_placeholders_test = unittest.make(_src_placeholders_test)
def _validate_attributes_test(ctx):
"""Unit tests for maprule_testing.validate_attributes."""
env = unittest.begin(ctx)
_assert_no_error(
env,
maprule_testing.validate_attributes({"FOO": "bar"}, {"BAR": "value1"}),
"assertion #1",
)
_assert_no_error(
env,
maprule_testing.validate_attributes({"FOO": "bar"}, {}),
"assertion #2",
)
_assert_error(
env,
maprule_testing.validate_attributes({}, {}),
"\"outs_templates\" must not be empty",
"assertion #3",
)
_assert_error(
env,
maprule_testing.validate_attributes({"": "foo"}, {}),
"name should not be empty",
"assertion #4",
)
_assert_error(
env,
maprule_testing.validate_attributes({"foo": "bar"}, {}),
"name should be all upper-case",
"assertion #5",
)
_assert_error(
env,
maprule_testing.validate_attributes({"SRC": "bar"}, {}),
"conflicting with the environment variable of the source file",
"assertion #6",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": ""}, {}),
"output path should not be empty",
"assertion #7",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "/usr/bin"}, {}),
"output path should be relative",
"assertion #8",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "c:/usr/bin"}, {}),
"output path should be relative",
"assertion #9",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "../foo"}, {}),
"output path should not contain uplevel references",
"assertion #10",
)
_assert_no_error(
env,
maprule_testing.validate_attributes({"FOO": "./foo"}, {}),
"assertion #11",
)
_assert_error(
env,
maprule_testing.validate_attributes({"BAR": "foo", "FOO": "foo"}, {}),
"output path is already used for \"BAR\"",
"assertion #12",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "bar"}, {"": "baz"}),
"name should not be empty",
"assertion #13",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "bar"}, {"Bar": "baz"}),
"name should be all upper-case",
"assertion #14",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "bar"}, {"FOO": "baz"}),
"conflicting with the environment variable of the \"FOO\" output file",
"assertion #15",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "bar"}, {"BAR": "$(location x) $(location y)"}),
"use only one $(location)",
"assertion #16",
)
_assert_error(
env,
maprule_testing.validate_attributes({"FOO": "bar"}, {"BAR": "a $(location b"}),
"missing closing parenthesis",
"assertion #17",
)
return unittest.end(env)
validate_attributes_test = unittest.make(_validate_attributes_test)
def _as_path_test(ctx):
"""Unit tests for maprule_testing.as_path."""
env = unittest.begin(ctx)
asserts.equals(
env,
"Foo\\Bar\\Baz\\Qux",
maprule_testing.cmd_strategy.as_path("Foo/Bar/Baz\\Qux"),
msg = "assertion #1",
)
asserts.equals(
env,
"Foo/Bar/Baz\\Qux",
maprule_testing.bash_strategy.as_path("Foo/Bar/Baz\\Qux"),
msg = "assertion #2",
)
return unittest.end(env)
as_path_test = unittest.make(_as_path_test)
def _assert_relative_path(env, path, index):
asserts.true(
env,
maprule_testing.is_relative_path(path),
msg = "assertion #%d" % index,
)
def _assert_not_relative_path(env, path, index):
asserts.false(
env,
maprule_testing.is_relative_path(path),
msg = "assertion #%d" % index,
)
def _is_relative_path_test(ctx):
"""Unit tests for maprule_testing.is_relative_path."""
env = unittest.begin(ctx)
_assert_relative_path(env, "Foo/Bar/Baz", 1)
_assert_relative_path(env, "Foo\\Bar\\Baz", 2)
_assert_relative_path(env, "Foo/Bar\\Baz", 3)
_assert_not_relative_path(env, "d:/Foo/Bar", 4)
_assert_not_relative_path(env, "D:/Foo/Bar", 5)
_assert_not_relative_path(env, "/Foo/Bar", 6)
_assert_not_relative_path(env, "\\Foo\\Bar", 7)
return unittest.end(env)
is_relative_path_test = unittest.make(_is_relative_path_test)
def _custom_envmap_test(ctx):
"""Unit tests for maprule_testing.custom_envmap."""
env = unittest.begin(ctx)
actual = {}
for language, strategy in [
("cmd", maprule_testing.cmd_strategy),
("bash", maprule_testing.bash_strategy),
]:
actual[language] = maprule_testing.custom_envmap(
ctx,
strategy,
src_placeholders = {"src_ph1": "Src/Ph1-value", "src_ph2": "Src/Ph2-value"},
outs_dict = {
"out1": _mock_file(ctx, language + "/Foo/Out1"),
"out2": _mock_file(ctx, language + "/Foo/Out2"),
},
resolved_add_env = {"ENV1": "Env1"},
)
_assert_dict_keys(
env,
["MAPRULE_SRC_PH1", "MAPRULE_SRC_PH2", "MAPRULE_OUT1", "MAPRULE_OUT2", "MAPRULE_ENV1"],
actual[language],
msg = "assertion #1 (language: %s)" % language,
)
actual[language]["MAPRULE_OUT1"] = _lstrip_until(actual[language]["MAPRULE_OUT1"], "Foo")
actual[language]["MAPRULE_OUT2"] = _lstrip_until(actual[language]["MAPRULE_OUT2"], "Foo")
asserts.equals(
env,
{
"MAPRULE_ENV1": "Env1",
"MAPRULE_OUT1": "Foo\\Out1",
"MAPRULE_OUT2": "Foo\\Out2",
"MAPRULE_SRC_PH1": "Src\\Ph1-value",
"MAPRULE_SRC_PH2": "Src\\Ph2-value",
},
actual["cmd"],
msg = "assertion #2",
)
asserts.equals(
env,
{
"MAPRULE_ENV1": "Env1",
"MAPRULE_OUT1": "Foo/Out1",
"MAPRULE_OUT2": "Foo/Out2",
"MAPRULE_SRC_PH1": "Src/Ph1-value",
"MAPRULE_SRC_PH2": "Src/Ph2-value",
},
actual["bash"],
msg = "assertion #3",
)
return unittest.end(env)
custom_envmap_test = unittest.make(_custom_envmap_test)
def _create_outputs_test(ctx):
"""Unit tests for maprule_testing.create_outputs."""
env = unittest.begin(ctx)
for language, strategy in [
("cmd", maprule_testing.cmd_strategy),
("bash", maprule_testing.bash_strategy),
]:
src1 = _mock_file(ctx, language + "/foo/src1.txt")
src2 = _mock_file(ctx, language + "/foo/src2.pb.h")
src3 = _mock_file(ctx, language + "/bar/src1.txt")
foreach_srcs = [src1, src2, src3]
outs_dicts, all_output_files, _, errors = (
maprule_testing.create_outputs(
ctx,
"my_maprule",
{
"OUT1": "{src}.out1",
"OUT2": "{src_dir}/out2/{src_name_noext}.out2",
},
strategy,
foreach_srcs,
)
)
_assert_no_error(env, errors, "assertion #1 (language: %s)" % language)
for output in all_output_files:
_dummy_generating_action(ctx, output)
_assert_dict_keys(
env,
foreach_srcs,
outs_dicts,
"assertion #2 (language: %s)" % language,
)
for src in foreach_srcs:
_assert_dict_keys(
env,
["OUT1", "OUT2"],
outs_dicts[src],
"assertion #3 (language: %s, src: %s)" % (language, src),
)
_assert_ends_with(
env,
"my_maprule_out/tests/%s/foo/src1.txt.out1" % language,
outs_dicts[src1]["OUT1"].path,
"assertion #4 (language: %s)" % language,
)
_assert_ends_with(
env,
"my_maprule_out/tests/%s/foo/out2/src1.out2" % language,
outs_dicts[src1]["OUT2"].path,
"assertion #5 (language: %s)" % language,
)
_assert_ends_with(
env,
"my_maprule_out/tests/%s/foo/src2.pb.h.out1" % language,
outs_dicts[src2]["OUT1"].path,
"assertion #6 (language: %s)" % language,
)
_assert_ends_with(
env,
"my_maprule_out/tests/%s/foo/out2/src2.pb.out2" % language,
outs_dicts[src2]["OUT2"].path,
"assertion #7 (language: %s)" % language,
)
_assert_ends_with(
env,
"my_maprule_out/tests/%s/bar/src1.txt.out1" % language,
outs_dicts[src3]["OUT1"].path,
"assertion #8 (language: %s)" % language,
)
_assert_ends_with(
env,
"my_maprule_out/tests/%s/bar/out2/src1.out2" % language,
outs_dicts[src3]["OUT2"].path,
"assertion #9 (language: %s)" % language,
)
expected = [
"my_maprule_out/tests/%s/foo/src1.txt.out1" % language,
"my_maprule_out/tests/%s/foo/out2/src1.out2" % language,
"my_maprule_out/tests/%s/foo/src2.pb.h.out1" % language,
"my_maprule_out/tests/%s/foo/out2/src2.pb.out2" % language,
"my_maprule_out/tests/%s/bar/src1.txt.out1" % language,
"my_maprule_out/tests/%s/bar/out2/src1.out2" % language,
]
for i in range(0, len(all_output_files)):
actual = _lstrip_until(all_output_files[i].path, "my_maprule_out")
asserts.equals(
env,
expected[i],
actual,
"assertion #10 (language: %s, index: %d)" % (language, i),
)
return unittest.end(env)
create_outputs_test = unittest.make(_create_outputs_test)
def _conflicting_outputs_test(ctx):
"""Unit tests for maprule_testing.create_outputs catching conflicting outputs."""
env = unittest.begin(ctx)
for language, strategy in [
("cmd", maprule_testing.cmd_strategy),
("bash", maprule_testing.bash_strategy),
]:
src1 = _mock_file(ctx, language + "/foo/src1.txt")
src2 = _mock_file(ctx, language + "/foo/src2.pb.h")
src3 = _mock_file(ctx, language + "/bar/src1.txt")
foreach_srcs = [src1, src2, src3]
_, all_output_files, _, errors = (
maprule_testing.create_outputs(
ctx,
"my_maprule",
{
"OUT1": "out1", # 3 conflicts
"OUT2": "{src_dir}/out2", # 2 conflicts
"OUT3": "out3/{src_name}", # 2 conflicts
},
strategy,
foreach_srcs,
)
)
for output in all_output_files:
_dummy_generating_action(ctx, output)
_assert_error_fragments(
env,
errors,
["out1", language + "/foo/src1.txt", "OUT1", language + "/foo/src2.pb.h", "OUT1"],
msg = "assertion #1 (language: %s)" % language,
)
_assert_error_fragments(
env,
errors,
["out2", language + "/foo/src1.txt", "OUT2", language + "/foo/src2.pb.h", "OUT2"],
msg = "assertion #2 (language: %s)" % language,
)
_assert_error_fragments(
env,
errors,
["out3/src1.txt", language + "/foo/src1.txt", "OUT3", language + "/bar/src1.txt", "OUT3"],
msg = "assertion #5 (language: %s)" % language,
)
return unittest.end(env)
conflicting_outputs_test = unittest.make(_conflicting_outputs_test)
def maprule_test_suite():
"""Creates the test targets and test suite for maprule.bzl tests."""
unittest.suite(
"maprule_tests",
src_placeholders_test,
validate_attributes_test,
as_path_test,
is_relative_path_test,
custom_envmap_test,
conflicting_outputs_test,
)

View File

@ -1,5 +1,6 @@
load("//rules:copy_file.bzl", "copy_file")
load("//rules:native_binary.bzl", "native_binary", "native_test")
load("@rules_cc//cc:defs.bzl", "cc_binary")
package(
default_testonly = 1,

View File

@ -1,6 +1,7 @@
load("//rules:diff_test.bzl", "diff_test")
load("//rules:run_binary.bzl", "run_binary")
load("//rules:write_file.bzl", "write_file")
load("@rules_cc//cc:defs.bzl", "cc_binary")
package(
default_testonly = 1,