Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame^] | 1 | # Copyright 2023 The ChromiumOS Authors |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Script to generate (+ upload) DLC artifacts.""" |
| 6 | |
| 7 | import logging |
| 8 | import os |
| 9 | import shutil |
| 10 | from typing import List |
| 11 | |
| 12 | from chromite.lib import commandline |
| 13 | from chromite.lib import cros_build_lib |
| 14 | from chromite.lib import dlc_lib |
| 15 | from chromite.lib import osutils |
| 16 | |
| 17 | |
| 18 | # Predefined salts. |
| 19 | _SHORT_SALT = "1337D00D" |
| 20 | |
| 21 | # Tarball extension with correct compression. |
| 22 | _TAR_COMP_EXT = ".tar.zst" |
| 23 | _META_OUT_FILE = dlc_lib.DLC_TMP_META_DIR + _TAR_COMP_EXT |
| 24 | |
| 25 | # Filenames. |
| 26 | _METADATA_FILE = "metadata" |
| 27 | |
| 28 | |
| 29 | def ParseArguments(argv: List[str]) -> commandline.ArgumentNamespace: |
| 30 | """Returns a namespace for the CLI arguments.""" |
| 31 | parser = commandline.ArgumentParser(description=__doc__) |
| 32 | parser.add_argument( |
| 33 | "--src-dir", |
| 34 | type="dir_exists", |
| 35 | required=True, |
| 36 | help="The directory to package as a DLC", |
| 37 | ) |
| 38 | # Support license addition here. For now, have users explicitly pass in a |
| 39 | # stub license path. |
| 40 | parser.add_argument( |
| 41 | "--license", |
| 42 | type="file_exists", |
| 43 | required=True, |
| 44 | help="The path to license, this should be the same license as the one" |
| 45 | " used within the package", |
| 46 | ) |
| 47 | parser.add_argument( |
| 48 | "--output-dir", |
| 49 | type="path", |
| 50 | help="The optional output directory to put artifacts into", |
| 51 | ) |
| 52 | parser.add_argument( |
| 53 | "--output-metadata-dir", |
| 54 | type="path", |
| 55 | help="The optional output directory to put metadata into", |
| 56 | ) |
| 57 | |
| 58 | parser.add_argument( |
| 59 | "--upload", |
| 60 | action="store_true", |
| 61 | help="Upload the DLC artifacts to google buckets", |
| 62 | ) |
| 63 | parser.add_argument( |
| 64 | "--uri-path", |
| 65 | type="gs_path", |
| 66 | help="The override for DLC image URI, check dlc_lib for default", |
| 67 | ) |
| 68 | parser.add_argument( |
| 69 | "--upload-dry-run", |
| 70 | action="store_true", |
| 71 | help="Dry run without actual upload", |
| 72 | ) |
| 73 | |
| 74 | # Salt for randomness DLC image randomness. |
| 75 | parser.add_argument( |
| 76 | "--disable-randomness", |
| 77 | action="store_true", |
| 78 | help="To disable randomizing hashes when generating DLC images", |
| 79 | ) |
| 80 | |
| 81 | # DLC required fields. |
| 82 | parser.add_argument( |
| 83 | "--id", |
| 84 | type=str, |
| 85 | required=True, |
| 86 | help="The DLC ID", |
| 87 | ) |
| 88 | parser.add_argument( |
| 89 | "--preallocated-blocks", |
| 90 | type=int, |
| 91 | required=True, |
| 92 | help="The preallocated number of blocks in 4KiB chunks", |
| 93 | ) |
| 94 | |
| 95 | # DLC optional fields. |
| 96 | parser.add_argument( |
| 97 | "--name", |
| 98 | type=str, |
| 99 | default="", |
| 100 | help="The name of the DLC in human friendly format", |
| 101 | ) |
| 102 | parser.add_argument( |
| 103 | "--description", |
| 104 | type=str, |
| 105 | default="", |
| 106 | help="The description of the DLC in human friendly format", |
| 107 | ) |
| 108 | parser.add_argument( |
| 109 | "--version", |
| 110 | type=str, |
| 111 | required=True, |
| 112 | help="The version of this DLC build", |
| 113 | ) |
| 114 | |
| 115 | opts = parser.parse_args(argv) |
| 116 | |
| 117 | dlc_lib.ValidateDlcIdentifier(opts.id) |
| 118 | |
| 119 | opts.Freeze() |
| 120 | |
| 121 | return opts |
| 122 | |
| 123 | |
| 124 | def GenerateDlcParams( |
| 125 | opts: commandline.ArgumentNamespace, |
| 126 | ) -> dlc_lib.EbuildParams: |
| 127 | """Generates and verifies DLC parameters based on options |
| 128 | |
| 129 | Args: |
| 130 | opts: The command line arguments. |
| 131 | |
| 132 | Returns: |
| 133 | The DLC ebuild parameters. |
| 134 | """ |
| 135 | params = dlc_lib.EbuildParams( |
| 136 | dlc_id=opts.id, |
| 137 | dlc_package="package", |
| 138 | fs_type=dlc_lib.SQUASHFS_TYPE, |
| 139 | pre_allocated_blocks=opts.preallocated_blocks, |
| 140 | version=opts.version, |
| 141 | name=opts.name, |
| 142 | description=opts.description, |
| 143 | # Add preloading support. |
| 144 | preload=False, |
| 145 | used_by="", |
| 146 | mount_file_required=False, |
| 147 | fullnamerev="", |
| 148 | scaled=True, |
| 149 | loadpin_verity_digest=False, |
| 150 | ) |
| 151 | params.VerifyDlcParameters() |
| 152 | return params |
| 153 | |
| 154 | |
| 155 | def UploadDlcArtifacts( |
| 156 | dlcartifacts: dlc_lib.DlcArtifacts, dry_run: bool |
| 157 | ) -> None: |
| 158 | """Uploads the DLC artifacts based on `DlcArtifacts` |
| 159 | |
| 160 | Args: |
| 161 | dlcartifacts: The DLC artifacts to upload. |
| 162 | dry_run: Dry run without actually uploading if true. |
| 163 | """ |
| 164 | logging.info("Uploading DLC artifacts") |
| 165 | logging.debug( |
| 166 | "Uploading DLC image %s to %s", |
| 167 | dlcartifacts.image, |
| 168 | dlcartifacts.uri_path, |
| 169 | ) |
| 170 | logging.debug( |
| 171 | "Uploading DLC meta %s to %s", dlcartifacts.meta, dlcartifacts.uri_path |
| 172 | ) |
| 173 | dlcartifacts.Upload(dry_run=dry_run) |
| 174 | |
| 175 | |
| 176 | def GenerateDlcArtifacts(opts: commandline.ArgumentNamespace) -> None: |
| 177 | """Generates the DLC artifacts |
| 178 | |
| 179 | Args: |
| 180 | opts: The command line arguments. |
| 181 | """ |
| 182 | params = GenerateDlcParams(opts) |
| 183 | uri_path = opts.uri_path or params.GetURIDir() |
| 184 | |
| 185 | with osutils.TempDir(prefix="dlcartifacts", sudo_rm=True) as tmpdir: |
| 186 | output_dir = opts.output_dir or tmpdir |
| 187 | os.makedirs(output_dir, exist_ok=True) |
| 188 | |
| 189 | logging.info("Generating DLC artifacts") |
| 190 | artifacts = dlc_lib.DlcGenerator( |
| 191 | src_dir=opts.src_dir, |
| 192 | sysroot="", |
| 193 | board=dlc_lib.MAGIC_BOARD, |
| 194 | ebuild_params=params, |
| 195 | reproducible=opts.disable_randomness, |
| 196 | license_file=opts.license, |
| 197 | ).ExternalGenerateDLC( |
| 198 | tmpdir, _SHORT_SALT if opts.disable_randomness else None |
| 199 | ) |
| 200 | logging.debug("Generated DLC artifacts: %s", artifacts.StringJSON()) |
| 201 | |
| 202 | # Handle the meta. |
| 203 | meta_out = os.path.join(output_dir, _META_OUT_FILE) |
| 204 | |
| 205 | logging.info("Emitting the metadata into %s", meta_out) |
| 206 | cros_build_lib.CreateTarball( |
| 207 | tarball_path=meta_out, |
| 208 | cwd=artifacts.meta, |
| 209 | compression=cros_build_lib.CompressionType.ZSTD, |
| 210 | extra_env={"ZSTD_CLEVEL": "9"}, |
| 211 | ) |
| 212 | |
| 213 | # Handle the image. |
| 214 | image_out = os.path.join(output_dir, dlc_lib.DLC_IMAGE) |
| 215 | logging.info("Emitting the DLC image into %s", image_out) |
| 216 | shutil.move(artifacts.image, image_out) |
| 217 | |
| 218 | # Handle the upload. |
| 219 | ret_artifacts = dlc_lib.DlcArtifacts( |
| 220 | uri_path=uri_path, |
| 221 | image=image_out, |
| 222 | meta=meta_out, |
| 223 | ) |
| 224 | ret_artifacts_json = ret_artifacts.StringJSON() |
| 225 | logging.debug("The final DLC artifacts: %s", ret_artifacts_json) |
| 226 | |
| 227 | if opts.output_metadata_dir: |
| 228 | osutils.WriteFile( |
| 229 | os.path.join(opts.output_metadata_dir, _METADATA_FILE), |
| 230 | ret_artifacts_json, |
| 231 | makedirs=True, |
| 232 | ) |
| 233 | |
| 234 | if opts.upload or opts.upload_dry_run: |
| 235 | UploadDlcArtifacts(ret_artifacts, opts.upload_dry_run) |
| 236 | else: |
| 237 | logging.debug("Skipping DLC artifacts upload") |
| 238 | |
| 239 | |
| 240 | def main(argv): |
| 241 | GenerateDlcArtifacts(ParseArguments(argv)) |