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