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