blob: 007bfa5f8ebd4b264d3586cadd23bd9bf594556a [file] [log] [blame]
László Csomor8d4f7612019-01-08 09:04:53 +01001# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Maprule implementation in Starlark.
16
17This module exports:
18
19- The cmd_maprule() and bash_maprule() build rules.
20 They are the same except for the interpreter they use (cmd.exe and Bash respectively) and for the
21 expected language of their `cmd` attribute. We will refer to them collectively as `maprule`.
22
23- The maprule_testing struct. This should only be used by maprule's own unittests.
24"""
25
26load("//lib:dicts.bzl", "dicts")
27load("//lib:paths.bzl", "paths")
28
29_cmd_maprule_intro = """
30Maprule that runs a Windows Command Prompt (`cmd.exe`) command.
31
32This rule is the same as `bash_maprule`, but uses `cmd.exe` instead of Bash, therefore the `cmd`
33attribute must use Command Prompt syntax. `cmd_maprule` rules can only be built on Windows.
34"""
35
36_bash_maprule_intro = """
37Maprule that runs a Bash command.
38
39This rule is the same as `cmd_maprule` except this one uses Bash to run the command, therefore the
40`cmd` attribute must use Bash syntax. `bash_maprule` rules can only be built if Bash is installed on
41the build machine.
42"""
43
44_cmd_maprule_example = """
45 # This file is //projects/game:BUILD
46
47 load("//tools/build_rules:maprule.bzl", "cmd_maprule")
48
49 cmd_maprule(
50 name = "process_assets",
51 foreach_srcs = [
52 "rust.png",
53 "teapot.3ds",
54 "//assets:wood.jpg",
55 "//assets:models",
56 ],
57 outs_templates = {
58 "TAGS": "{src}.tags",
59 "DIGEST": "digests/{src_name_noext}.md5",
60 },
61 tools = [
62 "//bin:asset-tagger",
63 "//util:md5sum",
64 ],
65 add_env = {
66 "ASSET_TAGGER": "$(location //bin:asset-tagger)",
67 "MD5SUM": "$(location //util:md5sum)",
68 },
69
70 # Note: this command should live in a script file, we only inline it in the `cmd` attribute
71 # for the sake of demonstration. See Tips and Tricks section.
72 cmd = "%MAPRULE_ASSET_TAGGER% --input=%MAPRULE_SRC% --output=%MAPRULE_TAGS% & " +
73 'IF /I "%ERRORLEVEL%" EQU "0" ( %MAPRULE_MD5SUM% %MAPRULE_SRC% > %MAPRULE_DIGEST% )',
74 )
75"""
76
77_bash_maprule_example = """
78 # This file is //projects/game:BUILD
79
80 load("//tools/build_rules:maprule.bzl", "bash_maprule")
81
82 bash_maprule(
83 name = "process_assets",
84 foreach_srcs = [
85 "rust.png",
86 "teapot.3ds",
87 "//assets:wood.jpg",
88 "//assets:models",
89 ],
90 outs_templates = {
91 "TAGS": "{src}.tags",
92 "DIGEST": "digests/{src_name_noext}.md5",
93 },
94 tools = [
95 "//bin:asset-tagger",
96 "//util:md5sum",
97 ],
98 add_env = {
99 "ASSET_TAGGER": "$(location //bin:asset-tagger)",
100 "MD5SUM": "$(location //util:md5sum)",
101 },
102
103 # Note: this command should live in a script file, we only inline it in the `cmd` attribute
104 # for the sake of demonstration. See Tips and Tricks section.
105 cmd = '"$MAPRULE_ASSET_TAGGER" --input="$MAPRULE_SRC" --output="$MAPRULE_TAGS" && ' +
106 '"$MAPRULE_MD5SUM" "$MAPRULE_SRC" > "$MAPRULE_DIGEST"',
107 )
108"""
109
110_rule_doc_template = """
111{intro}
112
113Maprule runs a specific command for each of the "foreach" source files. This allows processing
114source files in parallel, to produce some outputs for each of them.
115
116The name "maprule" indicates that this rule can be used to map source files to output files, and is
117also a reference to "genrule" that inspired this rule's design (though there are significant
118differences).
119
120Below you will find an Example, Tips and Tricks, and an FAQ.
121
122### Example
123
124{example}
125
126The "process_assets" rule above will execute the command in the `cmd` to process "rust.png",
127"teapot.3ds", "//assets:wood.jpg", and every file in the "//assets:models" rule, producing the
128corresponding .tags and .md5 files for each of them, under the following paths:
129
130 bazel-bin/projects/game/process_assets_out/projects/game/rust.png.tags
131 bazel-bin/projects/game/process_assets_out/digests/rust.md5
132 bazel-bin/projects/game/process_assets_out/projects/game/teapot.3ds.tags
133 bazel-bin/projects/game/process_assets_out/digests/teapot.md5
134 bazel-bin/projects/game/process_assets_out/assets/wood.jpg.tags
135 bazel-bin/projects/game/process_assets_out/digests/wood.md5
136 ...
137
138You can create downstream rules, for example a filegroup or genrule (or another maprule) that put
139this rule in their `srcs` attribute and get all the .tags and .md5 files.
140
141## Tips and Tricks
142
143*(The Tips and Tricks section is the same for `cmd_maprule` and `bash_maprule`.)*
144
145### Use script files instead of inlining commands in the `cmd` attribute.
146
147Unless the command is trivial, don't try to write it in `cmd`.
148
149Properly quoting parts of the command is challenging enough, add to that escaping for the BUILD
150file's syntax and the `cmd` attribute quickly gets unmaintainably complex.
151
152It's a lot easier and maintainable to create a script file such as "foo.sh" in `bash_maprule` or
153"foo.bat" in `cmd_maprule` for the commands. To do that:
154
1551. move the commands to a script file
1562. add the script file to the `tools` attribute
1573. add an entry to the `add_env` attribute, e.g. "`TOOL: "$(location :tool.sh)"`"
1584. replace the `cmd` with just "$MAPRULE_FOO" (in `bash_maprule`) or "%MAPRULE_FOO%" (in
159 `cmd_maprule`).
160
161Doing this also avoids hitting command line length limits.
162
163Example:
164
165 cmd_maprule(
166 ...
167 srcs = ["//data:temperatures.txt"],
168 tools = [
169 "command.bat",
170 ":weather-stats",
171 ],
172 add_env = {{
173 "COMMAND": "$(location :command.bat)",
174 "STATS_TOOL": "$(location :weather-stats-computer)",
175 "TEMPERATURES": "$(location //data:temperatures.txt)",
176 }},
177 cmd = "%MAPRULE_COMMAND%",
178 )
179
180### Use the `add_env` attribute to pass tool locations to the command.
181
182Entries in the `add_env` attribute may use "$(location)" references and may also use the same
183placeholders as the `outs_templates` attribute. For example you can use this mechanism to pass extra
184"$(location)" references of `tools` or `srcs` to the actions.
185
186Example:
187
188 cmd_maprule(
189 ...
190 foreach_srcs = [...],
191 outs_templates = {{"STATS_OUT": "{{src}}-stats.txt"}},
192 srcs = ["//data:temperatures.txt"],
193 tools = [":weather-stats"],
194 add_env = {{
195 "STATS_TOOL": "$(location :weather-stats-computer)",
196 "TEMPERATURES": "$(location //data:temperatures.txt)",
197 }},
198 cmd = "%MAPRULE_STATS_TOOL% --src=%MAPRULE_SRC% --data=%MAPRULE_TEMPERATURES% > %MAPRULE_STATS_OUT%",
199 )
200
201 cc_binary(
202 name = "weather-stats",
203 ...
204 )
205
206## Environment Variables
207
208*(The Environment Variables section is the same for `cmd_maprule` and `bash_maprule`.)*
209
210The rule defines several environment variables available to the command may reference. All of these
211envvars names start with "MAPRULE_". You can add your own envvars with the `add_env` attribute.
212
213The command can use some envvars, all named "MAPRULE_*<something>*".
214
215The complete list of environment variables is:
216
217- "MAPRULE_SRC": the path of the current file from `foreach_srcs`
218- "MAPRULE_SRCS": the space-separated paths of all files in the `srcs` attribute
219- "MAPRULE_*<OUT>*": for each key name *<OUT>* in the `outs_templates` attribute, this
220 is the path of the respective output file for the current source
221- "MAPRULE_*<ENV>*": for each key name *<ENV>* in the `add_env` attribute
222
223## FAQ
224
225*(The FAQ section is the same for `cmd_maprule` and `bash_maprule`.)*
226
227### What's the motivation for maprule? What's wrong with genrule?
228
229genrule creates a single action for all of its inputs. It requires specifying all output files.
230Finally, it can only create Bash commands.
231
232Maprule supports parallel processing of its inputs and doesn't require specifying all outputs, just
233templates for the outputs. And not only Bash is supported (via `bash_maprule`) but so are
234cmd.exe-style commands via `cmd_maprule`.
235
236### `genrule.cmd` supports "$(location)" expressions, how come `*_maprule.cmd` doesn't?
237
238Maprule deliberately omits support for this feature to avoid pitfalls with quoting and escaping, and
239potential issues with paths containing spaces. Instead, maprule exports environment variables for
240the input and outputs of the action, and allows the user to define extra envvars. These extra
241envvars do support "$(location)" expressions, so you can pass paths of labels in `srcs` and `tools`.
242
243### Why are all envvars exported with the "MAPRULE_" prefix?
244
245To avoid conflicts with other envvars, whose names could also be attractive outs_templates names.
246
247### Why do `outs_templates` and `add_env` keys have to be uppercase?
248
249Because they are exported as all-uppercase envvars, so requiring that they be declared as uppercase
250gives a visual cue of that. It also avoids clashes resulting from mixed lower-upper-case names like
251"foo" and "Foo".
252
253### Why don't `outs_templates` and `add_env` keys have to start with "MAPRULE_" even though they are exported as such?
254
255For convenience. It seems to bring no benefit to have the user always type "MAPRULE_" in front of
256the name when the rule itself could as well add it.
257
258### Why are all outputs relative to "*<maprule_pkg>*/*<maprule_name>*_out/" ?
259
260To ensure that maprules in the same package and with the same outs_templates produce distinct output
261files.
262
263### Why aren't `outs_templates` keys available as placeholders in the values of `add_env`?
264
265Because `add_env` is meant to be used for passing extra "$(location)" information to the action, and
266the output paths are already available as envvars for the action.
267"""
268
269def _is_relative_path(p):
270 """Returns True if `p` is a relative path (considering Unix and Windows semantics)."""
271 return p and p[0] != "/" and p[0] != "\\" and (
272 len(p) < 2 or not p[0].isalpha() or p[1] != ":"
273 )
274
275def _validate_attributes(ctx_attr_outs_templates, ctx_attr_add_env):
276 """Validates rule attributes and returns a list of error messages if there were errors."""
277 errors = []
278
279 envvars = {
280 "MAPRULE_SRC": "the source file",
281 "MAPRULE_SRCS": "the space-joined paths of the common sources",
282 }
283
284 if not ctx_attr_outs_templates:
285 errors.append("ERROR: \"outs_templates\" must not be empty")
286
287 names_to_paths = {}
288 paths_to_names = {}
289
290 # Check entries in "outs_templates".
291 for name, path in ctx_attr_outs_templates.items():
292 # Check the entry's name.
293 envvar_for_name = "MAPRULE_" + name.upper()
294 error_prefix = "ERROR: In \"outs_templates\" entry {\"%s\": \"%s\"}: " % (name, path)
295 if not name:
296 errors.append("ERROR: Bad entry in the \"outs_templates\" attribute: the name " +
297 "should not be empty.")
298 elif name.upper() != name:
299 errors.append(error_prefix + "the name should be all upper-case.")
300 elif envvar_for_name in envvars:
301 errors.append((error_prefix +
302 "please rename it, otherwise this output path would be exported " +
303 "as the environment variable \"%s\", conflicting with the " +
304 "environment variable of %s.") % (
305 envvar_for_name,
306 envvars[envvar_for_name],
307 ))
308 elif not path:
309 errors.append(error_prefix + "output path should not be empty.")
310 elif not _is_relative_path(path):
311 errors.append(error_prefix + "output path should be relative.")
312 elif ".." in path:
313 errors.append(error_prefix + "output path should not contain uplevel references.")
314 elif path in paths_to_names:
315 errors.append(error_prefix +
316 "output path is already used for \"%s\"." % paths_to_names[path])
317 envvars[envvar_for_name] = "the \"%s\" output file declared in the \"outs_templates\" attribute" % name
318 names_to_paths[name] = path
319 paths_to_names[path] = name
320
321 # Check envvar names in "add_env".
322 for name, value in ctx_attr_add_env.items():
323 envvar_for_name = "MAPRULE_" + name.upper()
324 error_prefix = "ERROR: In \"add_env\" entry {\"%s\": \"%s\"}: " % (name, value)
325 if not name:
326 errors.append("ERROR: Bad entry in the \"add_env\" attribute: the name should not be " +
327 "empty.")
328 elif name.upper() != name:
329 errors.append(error_prefix + "the name should be all upper-case.")
330 elif envvar_for_name in envvars:
331 errors.append((error_prefix +
332 "please rename it, otherwise it would be exported as \"%s\", " +
333 "conflicting with the environment variable of %s.") % (
334 envvar_for_name,
335 envvars[envvar_for_name],
336 ))
337 elif "$(location" in value:
338 tokens = value.split("$(location")
339 if len(tokens) != 2:
340 errors.append(error_prefix + "use only one $(location) or $(locations) function.")
341 elif ")" not in tokens[1]:
342 errors.append(error_prefix +
343 "incomplete $(location) or $(locations) function, missing closing " +
344 "parenthesis")
345 envvars[name] = "an additional environment declared in \"add_env\" as \"%s\"" % name
346
347 return errors
348
349def _src_placeholders(src, strategy):
350 return {
351 "src": strategy.as_path(src.short_path),
352 "src_dir": strategy.as_path(paths.dirname(src.short_path) + "/"),
353 "src_name": src.basename,
354 "src_name_noext": (src.basename[:-len(src.extension) - 1] if len(src.extension) else src.basename),
355 }
356
357def _create_outputs(ctx, ctx_label_name, ctx_attr_outs_templates, strategy, foreach_srcs):
358 errors = []
359 outs_dicts = {}
360 output_generated_by = {}
361 all_output_files = []
362 src_placeholders_dicts = {}
363 output_path_prefix = ctx_label_name + "_out/"
364 for src in foreach_srcs:
365 src_placeholders_dicts[src] = _src_placeholders(src, strategy)
366
367 outputs_for_src = {}
368 for template_name, path in ctx_attr_outs_templates.items():
369 output_path = path.format(**src_placeholders_dicts[src])
370 if output_path in output_generated_by:
371 existing_generator = output_generated_by[output_path]
372 errors.append("\n".join([
373 "ERROR: output file generated multiple times:",
374 " output file: " + output_path,
375 " foreach_srcs file 1: " + existing_generator[0].short_path,
376 " outs_templates entry 1: " + existing_generator[1],
377 " foreach_srcs file 2: " + src.short_path,
378 " outs_templates entry 2: " + template_name,
379 ]))
380 output_generated_by[output_path] = (src, template_name)
381 output = ctx.actions.declare_file(output_path_prefix + output_path)
382 outputs_for_src[template_name] = output
383 all_output_files.append(output)
384 outs_dicts[src] = outputs_for_src
385
386 if errors:
387 # For sake of Starlark unittest we return all_output_files, so the test can create dummy
388 # generating actions for the files even in case of errors.
389 return None, all_output_files, None, errors
390 else:
391 return outs_dicts, all_output_files, src_placeholders_dicts, None
392
393def _resolve_locations(ctx, strategy, ctx_attr_add_env, ctx_attr_tools):
394 # ctx.resolve_command returns a Bash command. All we need though is the inputs and runfiles
395 # manifests (we expand $(location) references with ctx.expand_location below), so ignore the
396 # tuple's middle element (the resolved command).
397 inputs_from_tools, _, manifests_from_tools = ctx.resolve_command(
398 # Pretend that the additional envvars are forming a command, so resolve_command will resolve
399 # $(location) references in them (which we ignore here) and add their inputs and manifests
400 # to the results (which we really care about).
401 command = " ".join(ctx_attr_add_env.values()),
402 tools = ctx_attr_tools,
403 )
404
405 errors = []
406 location_expressions = []
407 parts = {}
408 was_anything_to_resolve = False
409 for k, v in ctx_attr_add_env.items():
410 # Look for "$(location ...)" or "$(locations ...)", resolve if found.
411 # _validate_attributes already ensured that there's at most one $(location/s ...) in "v".
412 if "$(location" in v:
413 tokens = v.split("$(location")
414 was_anything_to_resolve = True
415 closing_paren = tokens[1].find(")")
416 location_expressions.append("$(location" + tokens[1][:closing_paren + 1])
417 parts[k] = (tokens[0], tokens[1][closing_paren + 1:])
418 else:
419 location_expressions.append("")
420
421 if errors:
422 return None, None, None, errors
423
424 resolved_add_env = {}
425 if was_anything_to_resolve:
426 # Resolve all $(location) expressions in one go. Should be faster than resolving them
427 # one-by-one.
428 all_location_expressions = "<split_here>".join(location_expressions)
429 all_resolved_locations = ctx.expand_location(all_location_expressions)
430 resolved_locations = strategy.as_path(all_resolved_locations).split("<split_here>")
431
432 i = 0
433
434 # Starlark dictionaries have a deterministic order of iteration, so the element order in
435 # "resolved_locations" matches the order in "location_expressions", i.e. the previous
436 # iteration order of "ctx_attr_add_env".
437 for k, v in ctx_attr_add_env.items():
438 if location_expressions[i]:
439 head, tail = parts[k]
440 resolved_add_env[k] = head + resolved_locations[i] + tail
441 else:
442 resolved_add_env[k] = v
443 i += 1
444 else:
445 resolved_add_env = ctx_attr_add_env
446
447 if errors:
448 return None, None, None, errors
449 else:
450 return inputs_from_tools, manifests_from_tools, resolved_add_env, None
451
452def _custom_envmap(ctx, strategy, src_placeholders, outs_dict, add_env):
453 return dicts.add(
454 {
455 "MAPRULE_" + k.upper(): strategy.as_path(v)
456 for k, v in src_placeholders.items()
457 },
458 {
459 "MAPRULE_" + k.upper(): strategy.as_path(v.path)
460 for k, v in outs_dict.items()
461 },
462 {
463 "MAPRULE_" + k.upper(): strategy.as_path(ctx.expand_location(v)).format(**src_placeholders)
464 for k, v in add_env.items()
465 },
466 )
467
468def _fail_if_errors(errors):
469 if errors:
470 # Don't overwhelm the user; report up to ten errors.
471 fail("\n".join(errors[:10]))
472
473def _maprule_main(ctx, strategy):
474 errors = _validate_attributes(ctx.attr.outs_templates, ctx.attr.add_env)
475 _fail_if_errors(errors)
476
477 # From "srcs": merge the depsets in the DefaultInfo.files of the targets.
478 common_srcs = depset(transitive = [t[DefaultInfo].files for t in ctx.attr.srcs])
479 common_srcs_list = common_srcs.to_list()
480
481 # From "foreach_srcs": by accessing the attribute's value through ctx.files (a list, not a
482 # depset), we flatten the depsets of DefaultInfo.files of the targets and merge them to a single
483 # list. This is fine, we would have to do this anyway, because we iterate over them later.
484 foreach_srcs = ctx.files.foreach_srcs
485
486 # Create the outputs for each file in "foreach_srcs".
487 foreach_src_outs_dicts, all_outputs, src_placeholders_dicts, errors = _create_outputs(
488 ctx,
489 ctx.label.name,
490 ctx.attr.outs_templates,
491 strategy,
492 foreach_srcs,
493 )
494 _fail_if_errors(errors)
495
496 progress_message = (ctx.attr.message or "Executing maprule") + " for %s" % ctx.label
497
498 # Create the part of the environment variables map that all actions will share.
499 common_envmap = dicts.add(
500 ctx.configuration.default_shell_env,
501 {"MAPRULE_SRCS": " ".join([strategy.as_path(p.path) for p in common_srcs_list])},
502 )
503
504 # Resolve $(location) references in "cmd" and in "add_env".
505 inputs_from_tools, manifests_from_tools, add_env, errors = _resolve_locations(
506 ctx,
507 strategy,
508 ctx.attr.add_env,
509 ctx.attr.tools,
510 )
511 _fail_if_errors(errors)
512
513 # Create actions for each of the "foreach" sources.
514 for src in foreach_srcs:
515 strategy.create_action(
516 ctx,
517 inputs = depset(direct = [src] + inputs_from_tools, transitive = [common_srcs]),
518 outputs = foreach_src_outs_dicts[src].values(),
519 # The custom envmap contains envvars specific to the current "src", such as MAPRULE_SRC.
520 env = common_envmap + _custom_envmap(
521 ctx,
522 strategy,
523 src_placeholders_dicts[src],
524 foreach_src_outs_dicts[src],
525 add_env,
526 ),
527 command = ctx.attr.cmd,
528 progress_message = progress_message,
529 manifests_from_tools = manifests_from_tools,
530 )
531
532 return [DefaultInfo(files = depset(all_outputs))]
533
534def _as_windows_path(s):
535 """Returns the input path as a Windows path (replaces all of "/" with "\")."""
536 return s.replace("/", "\\")
537
538def _unchanged_path(s):
539 """Returns the input string (path) unchanged."""
540 return s
541
542def _create_cmd_action(ctx, inputs, outputs, env, command, progress_message, manifests_from_tools):
543 """Create one action using cmd.exe for one of the "foreach" sources."""
544 ctx.actions.run(
545 inputs = inputs,
546 outputs = outputs,
547 executable = "cmd.exe",
548 env = env,
549 arguments = ["/C", command],
550 progress_message = progress_message,
551 mnemonic = "Maprule",
552 input_manifests = manifests_from_tools,
553 )
554
555def _create_bash_action(ctx, inputs, outputs, env, command, progress_message, manifests_from_tools):
556 """Create one action using Bash for one of the "foreach" sources."""
557 ctx.actions.run_shell(
558 inputs = inputs,
559 outputs = outputs,
560 env = env,
561 command = command,
562 progress_message = progress_message,
563 mnemonic = "Maprule",
564 input_manifests = manifests_from_tools,
565 )
566
567_CMD_STRATEGY = struct(
568 as_path = _as_windows_path,
569 create_action = _create_cmd_action,
570)
571
572_BASH_STRATEGY = struct(
573 as_path = _unchanged_path,
574 create_action = _create_bash_action,
575)
576
577def _cmd_maprule_impl(ctx):
578 return _maprule_main(ctx, _CMD_STRATEGY)
579
580def _bash_maprule_impl(ctx):
581 return _maprule_main(ctx, _BASH_STRATEGY)
582
583_ATTRS = {
584 "srcs": attr.label_list(
585 allow_files = True,
586 doc = "The set of source files common to all actions of this rule.",
587 ),
588 "add_env": attr.string_dict(
589 doc = "Extra environment variables to define for the actions. Every variable's name " +
590 "must be uppercase. Bazel will automatically prepend \"MAPRULE_\" to the name " +
591 "when exporting the variable for the action. The values may use \"$(location)\" " +
592 "expressions for labels declared in the `srcs` and `tools` attribute, and " +
593 "may reference the same placeholders as the values of the `outs_templates` " +
594 "attribute.",
595 ),
596 "cmd": attr.string(
597 mandatory = True,
598 doc = "The command to execute. It must be in the syntax corresponding to this maprule " +
599 "type, e.g. for `bash_maprule` this must be a Bash command, and for `cmd_maprule` " +
600 "a Windows Command Prompt (cmd.exe) command. Several environment variables are " +
601 "available for this command, storing values like the paths of the input and output " +
602 "files of the action. See the \"Environment Variables\" section for the complete " +
603 "list of environment variables available to this command.",
604 ),
605 "foreach_srcs": attr.label_list(
606 allow_files = True,
607 mandatory = True,
608 doc = "The set of sources that will be processed one by one in parallel, to produce " +
609 "the templated outputs. Each of these source files will will be processed " +
610 "individually by its own action.",
611 ),
612 "message": attr.string(
613 doc = "A custom progress message to display as the actions are executed.",
614 ),
615 "outs_templates": attr.string_dict(
616 allow_empty = False,
617 mandatory = True,
618 doc = "<p>Templates for the output files. Each key defines a name for an output file " +
619 "and the value specifies a path template for that output. For each of the " +
620 "files in `foreach_srcs` this rule creates one action that produces all of " +
621 "these outputs. The paths of the particular output files for that input are " +
622 "computed from the template. The ensure the resolved templates yield unique " +
623 "paths, the following placeholders are supported in the path " +
624 "templates:</p>" +
625 "<ul>" +
626 "<li>\"{src}\": same as \"{src_dir}/{src_name}\"</li>" +
627 "<li>\"{src_dir}\": package path of the source file, and a trailing \"/\"</li>" +
628 "<li>\"{src_name}\": base name of the source file</li>" +
629 "<li>\"{src_name_noext}\": same as \"{src_name}\" without the file extension</li>" +
630 "</ul>" +
631 "<p>You may also add extra path components to the templates, as long as the path " +
632 "template is relative and does not contain uplevel references (\"..\"). " +
633 "Placeholders will be replaced by the values corresponding to the respective " +
634 "input file in `foreach_srcs`. Every output file is generated under " +
635 "&lt;bazel_bin&gt;/path/to/maprule/&lt;maprule_name&gt; + \"_outs/\".</p>",
636 ),
637 "tools": attr.label_list(
638 cfg = "host",
639 allow_files = True,
640 doc = "Tools used by the command. The `cmd` attribute, and the values of the " +
641 "`add_env` attribute may reference these tools in \"$(location)\" expressions, " +
642 "similar to the genrule rule.",
643 ),
644}
645
646# Maprule that uses Windows cmd.exe as the interpreter.
647cmd_maprule = rule(
648 implementation = _cmd_maprule_impl,
649 doc = _rule_doc_template.format(
650 intro = _cmd_maprule_intro,
651 example = _cmd_maprule_example,
652 ),
653 attrs = _ATTRS,
654)
655
656# Maprule that uses Bash as the interpreter.
657bash_maprule = rule(
658 implementation = _bash_maprule_impl,
659 doc = _rule_doc_template.format(
660 intro = _bash_maprule_intro,
661 example = _bash_maprule_example,
662 ),
663 attrs = _ATTRS,
664)
665
666# Only used in unittesting maprule.
667maprule_testing = struct(
668 cmd_strategy = _CMD_STRATEGY,
669 bash_strategy = _BASH_STRATEGY,
670 src_placeholders = _src_placeholders,
671 validate_attributes = _validate_attributes,
672 is_relative_path = _is_relative_path,
673 custom_envmap = _custom_envmap,
674 create_outputs = _create_outputs,
675)