Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 1 | # Copyright 2022 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """build_image is used to build a Chromium OS image. |
| 6 | |
| 7 | Chromium OS comes in many different forms. This script can be used to build |
| 8 | the following: |
| 9 | |
| 10 | base - Pristine Chromium OS image. As similar to Chrome OS as possible. |
| 11 | dev [default] - Developer image. Like base but with additional dev packages. |
| 12 | test - Like dev, but with additional test specific packages and can be easily |
| 13 | used for automated testing using scripts like test_that, etc. |
| 14 | factory_install - Install shim for bootstrapping the factory test process. |
| 15 | Cannot be built along with any other image. |
| 16 | |
| 17 | Examples: |
| 18 | |
| 19 | build_image --board=<board> dev test - builds developer and test images. |
| 20 | build_image --board=<board> factory_install - builds a factory install shim. |
| 21 | |
| 22 | Note if you want to build an image with custom size partitions, either consider |
| 23 | adding a new disk layout in build_library/legacy_disk_layout.json OR use |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 24 | adjust-part. Here are a few examples: |
Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 25 | |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 26 | adjust-part='STATE:+1G' -- add one GB to the size the stateful partition |
| 27 | adjust-part='ROOT-A:-1G' -- remove one GB from the primary rootfs partition |
| 28 | adjust-part='STATE:=1G' -- make the stateful partition 1 GB |
Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 29 | """ |
| 30 | |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 31 | import argparse |
| 32 | import os |
Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 33 | from pathlib import Path |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 34 | from typing import List, Optional, Tuple |
Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 35 | |
| 36 | from chromite.lib import commandline |
| 37 | from chromite.lib import constants |
| 38 | from chromite.lib import cros_build_lib |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 39 | from chromite.service import image |
Ram Chandrasekar | 158b32a | 2022-07-15 17:06:13 +0000 | [diff] [blame] | 40 | from chromite.utils import timer |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 41 | |
| 42 | |
Ram Chandrasekar | f005fa7 | 2022-06-16 21:45:14 +0000 | [diff] [blame] | 43 | def build_shell_bool_style_args( |
| 44 | parser: commandline.ArgumentParser, |
| 45 | name: str, |
| 46 | default_val: bool, |
| 47 | help_str: str, |
| 48 | deprecation_note: str, |
| 49 | alternate_name: Optional[str] = None, |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 50 | additional_neg_options: Optional[List[str]] = None, |
| 51 | ) -> None: |
| 52 | """Build the shell boolean input argument equivalent. |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 53 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 54 | There are two cases which we will need to handle, |
| 55 | case 1: A shell boolean arg, which doesn't need to be re-worded in python. |
| 56 | case 2: A shell boolean arg, which needs to be re-worded in python. |
| 57 | Example below. |
| 58 | For Case 1, for a given input arg name 'argA', we create three python |
| 59 | arguments. |
| 60 | --argA, --noargA, --no-argA. The arguments --argA and --no-argA will be |
| 61 | retained after deprecating --noargA. |
| 62 | For Case 2, for a given input arg name 'arg_A' we need to use alternate |
| 63 | argument name 'arg-A'. we create four python arguments in this case. |
| 64 | --arg_A, --noarg_A, --arg-A, --no-arg-A. The first two arguments will be |
| 65 | deprecated later. |
| 66 | TODO(b/232566937): Remove the creation of --noargA in case 1 and --arg_A and |
| 67 | --noarg_A in case 2. |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 68 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 69 | Args: |
| 70 | parser: The parser to update. |
| 71 | name: The input argument name. This will be used as 'dest' variable name. |
| 72 | default_val: The default value to assign. |
| 73 | help_str: The help string for the input argument. |
| 74 | deprecation_note: A deprecation note to use. |
| 75 | alternate_name: Alternate argument to be used after deprecation. |
| 76 | additional_neg_options: Additional negative alias options to use. |
| 77 | """ |
| 78 | arg = f"--{name}" |
| 79 | shell_narg = f"--no{name}" |
| 80 | py_narg = f"--no-{name}" |
| 81 | alt_arg = f"--{alternate_name}" if alternate_name else None |
| 82 | alt_py_narg = f"--no-{alternate_name}" if alternate_name else None |
| 83 | default_val_str = f"{help_str} (Default: %(default)s)." |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 84 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 85 | if alternate_name: |
| 86 | _alternate_narg_list = [alt_py_narg] |
| 87 | if additional_neg_options: |
| 88 | _alternate_narg_list.extend(additional_neg_options) |
| 89 | |
| 90 | parser.add_argument( |
| 91 | alt_arg, |
| 92 | action="store_true", |
| 93 | default=default_val, |
| 94 | dest=name, |
| 95 | help=default_val_str, |
| 96 | ) |
| 97 | parser.add_argument( |
| 98 | *_alternate_narg_list, |
| 99 | action="store_false", |
| 100 | dest=name, |
| 101 | help="Don't " + help_str.lower(), |
| 102 | ) |
Ram Chandrasekar | f005fa7 | 2022-06-16 21:45:14 +0000 | [diff] [blame] | 103 | |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 104 | parser.add_argument( |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 105 | arg, |
| 106 | action="store_true", |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 107 | default=default_val, |
| 108 | dest=name, |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 109 | deprecated=deprecation_note % alt_arg if alternate_name else None, |
| 110 | help=default_val_str if not alternate_name else argparse.SUPPRESS, |
| 111 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 112 | parser.add_argument( |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 113 | shell_narg, |
| 114 | action="store_false", |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 115 | dest=name, |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 116 | deprecated=deprecation_note |
| 117 | % (alt_py_narg if alternate_name else py_narg), |
| 118 | help=argparse.SUPPRESS, |
| 119 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 120 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 121 | if not alternate_name: |
| 122 | _py_narg_list = [py_narg] |
| 123 | if additional_neg_options: |
| 124 | _py_narg_list.extend(additional_neg_options) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 125 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 126 | parser.add_argument( |
| 127 | *_py_narg_list, |
| 128 | action="store_false", |
| 129 | dest=name, |
| 130 | help="Don't " + help_str.lower(), |
| 131 | ) |
| 132 | |
| 133 | |
| 134 | def build_shell_string_style_args( |
| 135 | parser: commandline.ArgumentParser, |
| 136 | name: str, |
| 137 | default_val: Optional[str], |
| 138 | help_str: str, |
| 139 | deprecation_note: str, |
| 140 | alternate_name: str, |
| 141 | ) -> None: |
| 142 | """Build the shell string input argument equivalent. |
| 143 | |
| 144 | Args: |
| 145 | parser: The parser to update. |
| 146 | name: The input argument name. This will be used as 'dest' variable name. |
| 147 | default_val: The default value to assign. |
| 148 | help_str: The help string for the input argument. |
| 149 | deprecation_note: A deprecation note to use. |
| 150 | alternate_name: Alternate argument to be used after deprecation. |
| 151 | """ |
| 152 | default_val_str = ( |
| 153 | f"{help_str} (Default: %(default)s)." if default_val else help_str |
| 154 | ) |
Ram Chandrasekar | f005fa7 | 2022-06-16 21:45:14 +0000 | [diff] [blame] | 155 | |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 156 | parser.add_argument( |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 157 | f"--{alternate_name}", |
| 158 | dest=f"{name}", |
| 159 | default=default_val, |
| 160 | help=default_val_str, |
| 161 | ) |
| 162 | parser.add_argument( |
| 163 | f"--{name}", |
| 164 | deprecated=deprecation_note % f"--{alternate_name}", |
| 165 | help=argparse.SUPPRESS, |
| 166 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 167 | |
| 168 | |
| 169 | def get_parser() -> commandline.ArgumentParser: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 170 | """Creates the cmdline argparser, populates the options and description. |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 171 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 172 | Returns: |
| 173 | Argument parser. |
| 174 | """ |
| 175 | deprecation_note = "Argument will be removed January, 2023. Use %s instead." |
| 176 | parser = commandline.ArgumentParser(description=__doc__) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 177 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 178 | parser.add_argument( |
| 179 | "-b", |
| 180 | "--board", |
| 181 | "--build-target", |
| 182 | dest="board", |
| 183 | default=cros_build_lib.GetDefaultBoard(), |
| 184 | help="The board to build images for.", |
| 185 | ) |
| 186 | build_shell_string_style_args( |
| 187 | parser, |
| 188 | "adjust_part", |
| 189 | None, |
| 190 | "Adjustments to apply to partition table (LABEL:[+-=]SIZE) " |
| 191 | "e.g. ROOT-A:+1G.", |
| 192 | deprecation_note, |
| 193 | "adjust-partition", |
| 194 | ) |
| 195 | build_shell_string_style_args( |
| 196 | parser, |
| 197 | "output_root", |
| 198 | Path(constants.DEFAULT_BUILD_ROOT) / "images", |
| 199 | "Directory in which to place image result directories " |
| 200 | "(named by version).", |
| 201 | deprecation_note, |
| 202 | "output-root", |
| 203 | ) |
| 204 | build_shell_string_style_args( |
| 205 | parser, |
| 206 | "builder_path", |
| 207 | None, |
| 208 | "The build name to be installed on DUT during hwtest.", |
| 209 | deprecation_note, |
| 210 | "builder-path", |
| 211 | ) |
| 212 | build_shell_string_style_args( |
| 213 | parser, |
| 214 | "disk_layout", |
| 215 | "default", |
| 216 | "The disk layout type to use for this image.", |
| 217 | deprecation_note, |
| 218 | "disk-layout", |
| 219 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 220 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 221 | # Kernel related options. |
| 222 | group = parser.add_argument_group("Kernel Options") |
| 223 | build_shell_string_style_args( |
| 224 | group, |
| 225 | "enable_serial", |
| 226 | None, |
| 227 | "Enable serial port for printks. Example values: ttyS0.", |
| 228 | deprecation_note, |
| 229 | "enable-serial", |
| 230 | ) |
| 231 | group.add_argument( |
| 232 | "--kernel-loglevel", |
| 233 | type=int, |
| 234 | default=7, |
| 235 | help="The loglevel to add to the kernel command line. " |
| 236 | "(Default: %(default)s).", |
| 237 | ) |
| 238 | group.add_argument( |
| 239 | "--loglevel", |
| 240 | dest="kernel_loglevel", |
| 241 | type=int, |
| 242 | deprecated=deprecation_note % "kernel-loglevel", |
| 243 | help=argparse.SUPPRESS, |
| 244 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 245 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 246 | # Bootloader related options. |
| 247 | group = parser.add_argument_group("Bootloader Options") |
| 248 | build_shell_string_style_args( |
| 249 | group, |
| 250 | "boot_args", |
| 251 | "noinitrd", |
| 252 | "Additional boot arguments to pass to the commandline.", |
| 253 | deprecation_note, |
| 254 | "boot-args", |
| 255 | ) |
| 256 | build_shell_bool_style_args( |
| 257 | group, |
| 258 | "enable_bootcache", |
| 259 | False, |
| 260 | "Make all bootloaders to use boot cache.", |
| 261 | deprecation_note, |
| 262 | "enable-bootcache", |
| 263 | ) |
| 264 | build_shell_bool_style_args( |
| 265 | group, |
| 266 | "enable_rootfs_verification", |
| 267 | True, |
| 268 | "Make all bootloaders to use kernel based root-fs integrity checking.", |
| 269 | deprecation_note, |
| 270 | "enable-rootfs-verification", |
| 271 | ["-r"], |
| 272 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 273 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 274 | # Advanced options. |
| 275 | group = parser.add_argument_group("Advanced Options") |
| 276 | group.add_argument( |
| 277 | "--build-attempt", |
| 278 | type=int, |
| 279 | default=1, |
| 280 | help="The build attempt for this image build. (Default: %(default)s).", |
| 281 | ) |
| 282 | group.add_argument( |
| 283 | "--build_attempt", |
| 284 | type=int, |
| 285 | deprecated=deprecation_note % "build-attempt", |
| 286 | help=argparse.SUPPRESS, |
| 287 | ) |
| 288 | build_shell_string_style_args( |
| 289 | group, |
| 290 | "build_root", |
| 291 | Path(constants.DEFAULT_BUILD_ROOT) / "images", |
| 292 | "Directory in which to compose the image, before copying it to " |
| 293 | "output_root.", |
| 294 | deprecation_note, |
| 295 | "build-root", |
| 296 | ) |
| 297 | group.add_argument( |
| 298 | "-j", |
| 299 | "--jobs", |
| 300 | dest="jobs", |
| 301 | type=int, |
| 302 | default=os.cpu_count(), |
| 303 | help="Number of packages to build in parallel at maximum. " |
| 304 | "(Default: %(default)s).", |
| 305 | ) |
| 306 | build_shell_bool_style_args( |
| 307 | group, |
| 308 | "replace", |
| 309 | False, |
| 310 | "Overwrite existing output, if any.", |
| 311 | deprecation_note, |
| 312 | ) |
| 313 | group.add_argument( |
| 314 | "--symlink", |
| 315 | default="latest", |
| 316 | help="Symlink name to use for this image. (Default: %(default)s).", |
| 317 | ) |
| 318 | group.add_argument( |
| 319 | "--version", |
| 320 | default=None, |
| 321 | help="Overrides version number in name to this version.", |
| 322 | ) |
| 323 | build_shell_string_style_args( |
| 324 | group, |
| 325 | "output_suffix", |
| 326 | None, |
| 327 | "Add custom suffix to output directory.", |
| 328 | deprecation_note, |
| 329 | "output-suffix", |
| 330 | ) |
| 331 | group.add_argument( |
| 332 | "--eclean", |
| 333 | action="store_true", |
| 334 | default=True, |
| 335 | dest="eclean", |
| 336 | deprecated=( |
| 337 | "eclean is being removed from build_images. Argument will be " |
| 338 | "removed January, 2023." |
| 339 | ), |
| 340 | help=argparse.SUPPRESS, |
| 341 | ) |
| 342 | group.add_argument( |
| 343 | "--noeclean", |
| 344 | action="store_false", |
| 345 | dest="eclean", |
| 346 | deprecated=( |
| 347 | "eclean is being removed from build_images. Argument will be " |
| 348 | "removed January, 2023." |
| 349 | ), |
| 350 | help=argparse.SUPPRESS, |
| 351 | ) |
| 352 | group.add_argument( |
| 353 | "--no-eclean", |
| 354 | action="store_false", |
| 355 | dest="eclean", |
| 356 | deprecated=( |
| 357 | "eclean is being removed from build_images. Argument will be " |
| 358 | "removed January, 2023." |
| 359 | ), |
| 360 | help=argparse.SUPPRESS, |
| 361 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 362 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 363 | parser.add_argument( |
| 364 | "images", |
| 365 | nargs="*", |
| 366 | default=["dev"], |
| 367 | help="list of images to build. (Default: %(default)s).", |
| 368 | ) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 369 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 370 | return parser |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 371 | |
| 372 | |
| 373 | def parse_args( |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 374 | argv: List[str], |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 375 | ) -> Tuple[commandline.ArgumentParser, commandline.ArgumentNamespace]: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 376 | """Parse and validate CLI arguments. |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 377 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 378 | Args: |
| 379 | argv: Arguments passed via CLI. |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 380 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 381 | Returns: |
| 382 | Tuple having the below two, |
| 383 | Argument Parser |
| 384 | Validated argument namespace. |
| 385 | """ |
| 386 | parser = get_parser() |
| 387 | opts = parser.parse_args(argv) |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 388 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 389 | opts.build_run_config = image.BuildConfig( |
| 390 | adjust_partition=opts.adjust_part, |
| 391 | output_root=opts.output_root, |
| 392 | builder_path=opts.builder_path, |
| 393 | disk_layout=opts.disk_layout, |
| 394 | enable_serial=opts.enable_serial, |
| 395 | kernel_loglevel=opts.kernel_loglevel, |
| 396 | boot_args=opts.boot_args, |
| 397 | enable_bootcache=opts.enable_bootcache, |
| 398 | enable_rootfs_verification=opts.enable_rootfs_verification, |
| 399 | build_attempt=opts.build_attempt, |
| 400 | build_root=opts.build_root, |
| 401 | jobs=opts.jobs, |
| 402 | replace=opts.replace, |
| 403 | symlink=opts.symlink, |
| 404 | version=opts.version, |
| 405 | output_dir_suffix=opts.output_suffix, |
| 406 | ) |
| 407 | opts.Freeze() |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 408 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 409 | return parser, opts |
Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 410 | |
| 411 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 412 | @timer.timed("Elapsed time (build_image)") |
Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 413 | def main(argv: Optional[List[str]] = None) -> Optional[int]: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 414 | commandline.RunInsideChroot() |
| 415 | parser, opts = parse_args(argv) |
Ram Chandrasekar | 913c042 | 2022-05-09 23:41:22 +0000 | [diff] [blame] | 416 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 417 | # If the opts.board is not set, then it means user hasn't specified a default |
| 418 | # board in 'src/scripts/.default_board' and didn't specify it as input |
| 419 | # argument. |
| 420 | if not opts.board: |
| 421 | parser.error("--board is required") |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 422 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 423 | invalid_image = [ |
| 424 | x for x in opts.images if x not in constants.IMAGE_TYPE_TO_NAME |
| 425 | ] |
| 426 | if invalid_image: |
| 427 | parser.error(f"Invalid image type argument(s) {invalid_image}") |
Ram Chandrasekar | 355c8cc | 2022-05-24 23:10:30 +0000 | [diff] [blame] | 428 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame^] | 429 | result = image.Build(opts.board, opts.images, opts.build_run_config) |
| 430 | if result.run_error: |
| 431 | cros_build_lib.Die( |
| 432 | f"Error running build_image. Exit Code : {result.return_code}" |
| 433 | ) |