|
| 1 | +# Copyright 2021 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 | +"""Utilities for collecting and serializing package content. |
| 15 | +
|
| 16 | +This module provides the building blocks for implementing custom packaging rules |
| 17 | +on top of `rules_pkg`. A typical custom rule implementation will: |
| 18 | +
|
| 19 | +1. Call `create_mapping_context_from_ctx` to set up a `MappingContext`. |
| 20 | +2. Call `add_label_list` to resolve source labels into a content map. |
| 21 | +3. Call `write_manifest` to serialize the content map as a JSON manifest. |
| 22 | +
|
| 23 | +Example usage in a custom rule implementation: |
| 24 | +
|
| 25 | +```starlark |
| 26 | +load("@rules_pkg//pkg:package_content.bzl", |
| 27 | + "add_label_list", |
| 28 | + "create_mapping_context_from_ctx", |
| 29 | + "write_manifest", |
| 30 | +) |
| 31 | +
|
| 32 | +def _my_packager_impl(ctx): |
| 33 | + mapping_context = create_mapping_context_from_ctx(ctx, ctx.label) |
| 34 | + add_label_list(mapping_context, ctx.attr.srcs) |
| 35 | +
|
| 36 | + manifest = ctx.actions.declare_file(ctx.attr.name + "-manifest.json") |
| 37 | + write_manifest(ctx, manifest, mapping_context.content_map) |
| 38 | + ... |
| 39 | +``` |
| 40 | +""" |
| 41 | + |
| 42 | +load("//pkg:path.bzl", "compute_data_path") |
| 43 | +load( |
| 44 | + "//pkg:providers.bzl", |
| 45 | + "PackageDirsInfo", |
| 46 | + "PackageFilegroupInfo", |
| 47 | + "PackageFilesInfo", |
| 48 | + "PackageSymlinkInfo", |
| 49 | +) |
| 50 | +load( |
| 51 | + "//pkg/private:pkg_files.bzl", |
| 52 | + "MappingContext", |
| 53 | + "add_from_default_info", |
| 54 | + "encode_manifest_entry", |
| 55 | + "process_pkg_dirs", |
| 56 | + "process_pkg_filegroup", |
| 57 | + "process_pkg_files", |
| 58 | + "process_pkg_symlink", |
| 59 | +) |
| 60 | + |
| 61 | +# buildifier: disable=function-docstring-args |
| 62 | +def create_mapping_context_from_ctx( |
| 63 | + ctx, |
| 64 | + label, |
| 65 | + allow_duplicates_with_different_content = None, |
| 66 | + strip_prefix = None, |
| 67 | + include_runfiles = None, |
| 68 | + default_mode = None, |
| 69 | + path_mapper = None): |
| 70 | + """Construct a MappingContext. |
| 71 | +
|
| 72 | + Args: See the provider definition. |
| 73 | +
|
| 74 | + Returns: |
| 75 | + MappingContext |
| 76 | + """ |
| 77 | + if allow_duplicates_with_different_content == None: |
| 78 | + allow_duplicates_with_different_content = ctx.attr.allow_duplicates_with_different_content if hasattr(ctx.attr, "allow_duplicates_with_different_content") else False |
| 79 | + if strip_prefix == None: |
| 80 | + strip_prefix = ctx.attr.strip_prefix if hasattr(ctx.attr, "strip_prefix") else "" |
| 81 | + if include_runfiles == None: |
| 82 | + include_runfiles = ctx.attr.include_runfiles if hasattr(ctx.attr, "include_runfiles") else False |
| 83 | + if default_mode == None: |
| 84 | + default_mode = ctx.attr.mode if hasattr(ctx.attr, "default_mode") else "" |
| 85 | + |
| 86 | + return MappingContext( |
| 87 | + content_map = dict(), |
| 88 | + file_deps_direct = list(), |
| 89 | + file_deps_transitive = list(), |
| 90 | + label = label, |
| 91 | + allow_duplicates_with_different_content = allow_duplicates_with_different_content, |
| 92 | + strip_prefix = strip_prefix, |
| 93 | + include_runfiles = include_runfiles, |
| 94 | + workspace_name = ctx.workspace_name, |
| 95 | + default_mode = default_mode, |
| 96 | + path_mapper = path_mapper or (lambda x: x), |
| 97 | + # TODO(aiuto): allow these to be passed in as needed. But, before doing |
| 98 | + # that, explore defauilt_uid/gid as 0 rather than None |
| 99 | + default_user = "", |
| 100 | + default_group = "", |
| 101 | + default_uid = None, |
| 102 | + default_gid = None, |
| 103 | + ) |
| 104 | + |
| 105 | +def process_src(mapping_context, src, origin): |
| 106 | + """Add an entry to the content map. |
| 107 | +
|
| 108 | + Args: |
| 109 | + mapping_context: (r/w) a MappingContext |
| 110 | + src: Source Package*Info object |
| 111 | + origin: The rule instance adding this entry |
| 112 | +
|
| 113 | + Returns: |
| 114 | + True if src was a Package*Info and added to content_map. |
| 115 | + """ |
| 116 | + |
| 117 | + # Gather the files for every srcs entry here, even if it is not from |
| 118 | + # a pkg_* rule. |
| 119 | + if DefaultInfo in src: |
| 120 | + mapping_context.file_deps_transitive.append(src[DefaultInfo].files) |
| 121 | + found_info = False |
| 122 | + if PackageFilesInfo in src: |
| 123 | + process_pkg_files( |
| 124 | + mapping_context, |
| 125 | + src[PackageFilesInfo], |
| 126 | + origin, |
| 127 | + ) |
| 128 | + found_info = True |
| 129 | + if PackageFilegroupInfo in src: |
| 130 | + process_pkg_filegroup( |
| 131 | + mapping_context, |
| 132 | + src[PackageFilegroupInfo], |
| 133 | + ) |
| 134 | + found_info = True |
| 135 | + if PackageSymlinkInfo in src: |
| 136 | + process_pkg_symlink( |
| 137 | + mapping_context, |
| 138 | + src[PackageSymlinkInfo], |
| 139 | + origin, |
| 140 | + ) |
| 141 | + found_info = True |
| 142 | + if PackageDirsInfo in src: |
| 143 | + process_pkg_dirs( |
| 144 | + mapping_context, |
| 145 | + src[PackageDirsInfo], |
| 146 | + origin, |
| 147 | + ) |
| 148 | + found_info = True |
| 149 | + return found_info |
| 150 | + |
| 151 | +def add_label_list(mapping_context, srcs): |
| 152 | + """Helper method to add a list of labels (typically 'srcs') to a content_map. |
| 153 | +
|
| 154 | + Args: |
| 155 | + mapping_context: (r/w) a MappingContext |
| 156 | + srcs: List of source objects |
| 157 | + """ |
| 158 | + |
| 159 | + # Compute the relative path |
| 160 | + data_path = compute_data_path( |
| 161 | + mapping_context.label, |
| 162 | + mapping_context.strip_prefix, |
| 163 | + ) |
| 164 | + data_path_without_prefix = compute_data_path( |
| 165 | + mapping_context.label, |
| 166 | + ".", |
| 167 | + ) |
| 168 | + |
| 169 | + for src in srcs: |
| 170 | + if not process_src( |
| 171 | + mapping_context, |
| 172 | + src = src, |
| 173 | + origin = src.label, |
| 174 | + ): |
| 175 | + # Add in the files of srcs which are not pkg_* types |
| 176 | + add_from_default_info( |
| 177 | + mapping_context, |
| 178 | + src, |
| 179 | + data_path, |
| 180 | + data_path_without_prefix, |
| 181 | + mapping_context.include_runfiles, |
| 182 | + mapping_context.workspace_name, |
| 183 | + ) |
| 184 | + |
| 185 | +def write_manifest(ctx, manifest_file, content_map, use_short_path = False, pretty_print = False): |
| 186 | + """Write a content map to a manifest file. |
| 187 | +
|
| 188 | + The format of this file is currently undocumented, as it is a private |
| 189 | + contract between the rule implementation and the package writers. It will |
| 190 | + become a published interface in a future release. |
| 191 | +
|
| 192 | + For reproducibility, the manifest file must be ordered consistently. |
| 193 | +
|
| 194 | + Args: |
| 195 | + ctx: rule context |
| 196 | + manifest_file: File object used as the output destination |
| 197 | + content_map: content_map (see concepts at top of file) |
| 198 | + use_short_path: write out the manifest file destinations in terms of "short" paths, suitable for `bazel run`. |
| 199 | + pretty_print: indent the output nicely. Takes more space so it is off by default. |
| 200 | + """ |
| 201 | + ctx.actions.write( |
| 202 | + manifest_file, |
| 203 | + "[\n" + ",\n".join( |
| 204 | + [ |
| 205 | + encode_manifest_entry(ctx, dst, content_map[dst], use_short_path, pretty_print) |
| 206 | + for dst in sorted(content_map.keys()) |
| 207 | + ], |
| 208 | + ) + "\n]\n", |
| 209 | + ) |
0 commit comments