• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Rule to support Bazel in copying its output files to the dist dir outside of
2# the standard Bazel output user root.
3
4load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
5load("//build/bazel_common_rules/exec:embedded_exec.bzl", "embedded_exec")
6
7def _label_list_to_manifest(lst):
8    """Convert the outputs of a label list to manifest content."""
9    all_dist_files = []
10    for f in lst:
11        all_dist_files += f[DefaultInfo].files.to_list()
12    return all_dist_files, "\n".join([dist_file.short_path for dist_file in all_dist_files])
13
14def _generate_dist_manifest_impl(ctx):
15    # Create a manifest of dist files to differentiate them from other runfiles.
16    dist_manifest = ctx.actions.declare_file(ctx.attr.name + "_dist_manifest.txt")
17    all_dist_files, dist_manifest_content = _label_list_to_manifest(ctx.attr.data)
18    ctx.actions.write(
19        output = dist_manifest,
20        content = dist_manifest_content,
21    )
22
23    dist_archives_manifest = ctx.actions.declare_file(ctx.attr.name + "_dist_archives_manifest.txt")
24    all_dist_archives, dist_archives_manifest_content = _label_list_to_manifest(ctx.attr.archives)
25    ctx.actions.write(
26        output = dist_archives_manifest,
27        content = dist_archives_manifest_content,
28    )
29
30    # Create the runfiles object.
31    runfiles = ctx.runfiles(files = all_dist_files + all_dist_archives + [
32        dist_manifest,
33        dist_archives_manifest,
34    ])
35
36    return [DefaultInfo(runfiles = runfiles)]
37
38_generate_dist_manifest = rule(
39    implementation = _generate_dist_manifest_impl,
40    doc = """Generate a manifest of files to be dist to a directory.""",
41    attrs = {
42        "data": attr.label_list(
43            allow_files = True,
44            doc = """Files or targets to copy to the dist dir.
45
46In the case of targets, the rule copies the list of `files` from the target's DefaultInfo provider.
47""",
48        ),
49        "archives": attr.label_list(
50            allow_files = [".tar.gz", ".tar"],
51            doc = """Files or targets to be extracted to the dist dir.
52
53In the case of targets, the rule copies the list of `files` from the target's DefaultInfo provider.
54""",
55        ),
56    },
57)
58
59def copy_to_dist_dir(
60        name,
61        data = None,
62        archives = None,
63        flat = None,
64        prefix = None,
65        strip_components = 0,
66        archive_prefix = None,
67        dist_dir = None,
68        wipe_dist_dir = None,
69        allow_duplicate_filenames = None,
70        mode_overrides = None,
71        log = None,
72        **kwargs):
73    """A dist rule to copy files out of Bazel's output directory into a custom location.
74
75    Example:
76    ```
77    bazel run //path/to/my:dist_target -- --dist_dir=/tmp/dist
78    ```
79
80    Run `bazel run //path/to/my:dist_target -- --help` for explanations of
81    options.
82
83    Args:
84        name: name of this rule
85        data: A list of labels, whose outputs are copied to `--dist_dir`.
86        archives: A list of labels, whose outputs are treated as tarballs and
87          extracted to `--dist_dir`.
88        flat: If true, `--flat` is provided to the script by default. Flatten the distribution
89          directory.
90        strip_components: If specified, `--strip_components <prefix>` is provided to the script. Strip
91          leading components from the existing copied file paths before applying --prefix
92          (if specified).
93        prefix: If specified, `--prefix <prefix>` is provided to the script by default. Path prefix
94          to apply within dist_dir for copied files.
95        archive_prefix: If specified, `--archive_prefix <prefix>` is provided to the script by
96          default. Path prefix to apply within dist_dir for extracted archives.
97        dist_dir: If specified, `--dist_dir <dist_dir>` is provided to the script by default.
98
99          In particular, if this is a relative path, it is interpreted as a relative path
100          under workspace root when the target is executed with `bazel run`.
101
102          By default, the script will overwrite any files of the same name in `dist_dir`, but preserve
103          any other contents there. This can be overridden with `wipe_dist_dir`.
104
105          See details by running the target with `--help`.
106        wipe_dist_dir: If true, and `dist_dir` already exists, `dist_dir` will be removed prior to
107          copying.
108        allow_duplicate_filenames: If true, duplicate filenames from different sources will be allowed to
109          be copied to the same `dist_dir` (with subsequent sources overwriting previous sources).
110
111          With this option enabled, order matters. The final source of the file listed in `data` will be the
112          final version copied.
113
114          Use of this option is discouraged. Preferably, the input `data` targets would not include labels
115          which produce a duplicate filename. This option is available as a last resort.
116        mode_overrides: Map of glob patterns to octal permissions. If the file path being copied matches the
117          glob pattern, the corresponding permissions will be set in `dist_dir`. Full file paths are used for
118          matching even if `flat = True`. Paths are relative to the workspace root.
119
120          Order matters; the overrides will be stepped through in the order given for each file. To prevent
121          buildifier from sorting the list, use the `# do not sort` magic line. For example:
122          ```
123          mode_overrides = {
124              # do not sort
125              "**/*.sh": 755,
126              "**/hello_world": 755,
127              "restricted_dir/**": 600,
128              "common/kernel_aarch64/vmlinux": 755,
129              "**/*": 644,
130          },
131          ```
132
133          If no `mode_overrides` are provided, the default Bazel output permissions are preserved.
134        log: If specified, `--log <log>` is provided to the script by default. This sets the
135          default log level of the script.
136
137          See `dist.py` for allowed values and the default value.
138        kwargs: Additional attributes to the internal rule, e.g.
139          [`visibility`](https://docs.bazel.build/versions/main/visibility.html).
140
141          These additional attributes are only passed to the underlying embedded_exec rule.
142    """
143
144    default_args = []
145    if flat:
146        default_args.append("--flat")
147    if strip_components != None:
148        if strip_components < 0:
149            fail("strip_components must greater than 0, but is %s" % strip_components)
150        default_args += ["--strip_components", str(strip_components)]
151    if prefix != None:
152        default_args += ["--prefix", prefix]
153    if archive_prefix != None:
154        default_args += ["--archive_prefix", archive_prefix]
155    if dist_dir != None:
156        default_args += ["--dist_dir", dist_dir]
157    if wipe_dist_dir:
158        default_args.append("--wipe_dist_dir")
159    if allow_duplicate_filenames:
160        default_args.append("--allow_duplicate_filenames")
161    if mode_overrides != None:
162        for (pattern, mode) in mode_overrides.items():
163            default_args += ["--mode_override", pattern, str(mode)]
164    if log != None:
165        default_args += ["--log", log]
166
167    _generate_dist_manifest(
168        name = name + "_dist_manifest",
169        data = data,
170        archives = archives,
171    )
172
173    copy_file(
174        name = name + "_dist_tool",
175        src = "//build/bazel_common_rules/dist:dist.py",
176        out = name + "_dist.py",
177    )
178
179    # The dist py_binary tool must be colocated in the same package as the
180    # dist_manifest so that the runfiles directory is the same, and that the
181    # dist_manifest is in the data runfiles of the dist tool.
182    native.py_binary(
183        name = name + "_internal",
184        main = name + "_dist.py",
185        srcs = [name + "_dist.py"],
186        python_version = "PY3",
187        visibility = ["//visibility:public"],
188        data = [name + "_dist_manifest"],
189        args = default_args,
190    )
191
192    embedded_exec(
193        name = name,
194        actual = name + "_internal",
195        **kwargs
196    )
197