1# Copyright (c) 2018 Foundries.io 2# 3# SPDX-License-Identifier: Apache-2.0 4 5import abc 6import argparse 7import os 8import pathlib 9import pickle 10import platform 11import shutil 12import subprocess 13import sys 14 15from elftools.elf.elffile import ELFFile 16 17from west import manifest 18from west.commands import Verbosity 19from west.util import quote_sh_list 20 21from build_helpers import find_build_dir, is_zephyr_build, \ 22 FIND_BUILD_DIR_DESCRIPTION 23from runners.core import BuildConfiguration 24from zcmake import CMakeCache 25from zephyr_ext_common import Forceable, ZEPHYR_SCRIPTS 26 27# This is needed to load edt.pickle files. 28sys.path.insert(0, str(ZEPHYR_SCRIPTS / 'dts' / 'python-devicetree' / 'src')) 29 30SIGN_DESCRIPTION = '''\ 31This command automates some of the drudgery of creating signed Zephyr 32binaries for chain-loading by a bootloader. 33 34In the simplest usage, run this from your build directory: 35 36 west sign -t your_tool -- ARGS_FOR_YOUR_TOOL 37 38The "ARGS_FOR_YOUR_TOOL" value can be any additional arguments you want to 39pass to the tool, such as the location of a signing key etc. Depending on 40which sort of ARGS_FOR_YOUR_TOOLS you use, the `--` separator/sentinel may 41not always be required. To avoid ambiguity and having to find and 42understand POSIX 12.2 Guideline 10, always use `--`. 43 44See tool-specific help below for details.''' 45 46SIGN_EPILOG = '''\ 47imgtool 48------- 49 50To build a signed binary you can load with MCUboot using imgtool, 51run this from your build directory: 52 53 west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem 54 55For this to work, either imgtool must be installed (e.g. using pip3), 56or you must pass the path to imgtool.py using the -p option. 57 58Assuming your binary was properly built for processing and handling by 59imgtool, this creates zephyr.signed.bin and zephyr.signed.hex 60files which are ready for use by your bootloader. 61 62The version number, image header size, alignment, and slot sizes are 63determined from the build directory using .config and the device tree. 64As shown above, extra arguments after a '--' are passed to imgtool 65directly. 66 67rimage 68------ 69 70To create a signed binary with the rimage tool, run this from your build 71directory: 72 73 west sign -t rimage -- -k YOUR_SIGNING_KEY.pem 74 75For this to work, either rimage must be installed or you must pass 76the path to rimage using the -p option. 77 78You can also pass additional arguments to rimage thanks to [sign] and 79[rimage] sections in your west config file(s); this is especially useful 80when invoking west sign _indirectly_ through CMake/ninja. See how at 81https://docs.zephyrproject.org/latest/develop/west/sign.html 82''' 83 84class ToggleAction(argparse.Action): 85 86 def __call__(self, parser, args, ignored, option): 87 setattr(args, self.dest, not option.startswith('--no-')) 88 89 90class Sign(Forceable): 91 def __init__(self): 92 super(Sign, self).__init__( 93 'sign', 94 # Keep this in sync with the string in west-commands.yml. 95 'sign a Zephyr binary for bootloader chain-loading', 96 SIGN_DESCRIPTION, 97 accepts_unknown_args=False) 98 99 def do_add_parser(self, parser_adder): 100 parser = parser_adder.add_parser( 101 self.name, 102 epilog=SIGN_EPILOG, 103 help=self.help, 104 formatter_class=argparse.RawDescriptionHelpFormatter, 105 description=self.description) 106 107 parser.add_argument('-d', '--build-dir', 108 help=FIND_BUILD_DIR_DESCRIPTION) 109 parser.add_argument('-q', '--quiet', action='store_true', 110 help='suppress non-error output') 111 self.add_force_arg(parser) 112 113 # general options 114 group = parser.add_argument_group('tool control options') 115 group.add_argument('-t', '--tool', choices=['imgtool', 'rimage'], 116 help='''image signing tool name; imgtool and rimage 117 are currently supported (imgtool is deprecated)''') 118 group.add_argument('-p', '--tool-path', default=None, 119 help='''path to the tool itself, if needed''') 120 group.add_argument('-D', '--tool-data', default=None, 121 help='''path to a tool-specific data/configuration directory, if needed''') 122 group.add_argument('--if-tool-available', action='store_true', 123 help='''Do not fail if the rimage tool is not found or the rimage signing 124schema (rimage "target") is not defined in board.cmake.''') 125 group.add_argument('tool_args', nargs='*', metavar='tool_opt', 126 help='extra option(s) to pass to the signing tool') 127 128 # bin file options 129 group = parser.add_argument_group('binary (.bin) file options') 130 group.add_argument('--bin', '--no-bin', dest='gen_bin', nargs=0, 131 action=ToggleAction, 132 help='''produce a signed .bin file? 133 (default: yes, if supported and unsigned bin 134 exists)''') 135 group.add_argument('-B', '--sbin', metavar='BIN', 136 help='''signed .bin file name 137 (default: zephyr.signed.bin in the build 138 directory, next to zephyr.bin)''') 139 140 # hex file options 141 group = parser.add_argument_group('Intel HEX (.hex) file options') 142 group.add_argument('--hex', '--no-hex', dest='gen_hex', nargs=0, 143 action=ToggleAction, 144 help='''produce a signed .hex file? 145 (default: yes, if supported and unsigned hex 146 exists)''') 147 group.add_argument('-H', '--shex', metavar='HEX', 148 help='''signed .hex file name 149 (default: zephyr.signed.hex in the build 150 directory, next to zephyr.hex)''') 151 152 return parser 153 154 def do_run(self, args, ignored): 155 self.args = args # for check_force 156 157 # Find the build directory and parse .config and DT. 158 build_dir = find_build_dir(args.build_dir) 159 self.check_force(os.path.isdir(build_dir), 160 'no such build directory {}'.format(build_dir)) 161 self.check_force(is_zephyr_build(build_dir), 162 "build directory {} doesn't look like a Zephyr build " 163 'directory'.format(build_dir)) 164 build_conf = BuildConfiguration(build_dir) 165 166 if not args.tool: 167 args.tool = self.config_get('sign.tool') 168 169 # Decide on output formats. 170 formats = [] 171 bin_exists = build_conf.getboolean('CONFIG_BUILD_OUTPUT_BIN') 172 if args.gen_bin: 173 self.check_force(bin_exists, 174 '--bin given but CONFIG_BUILD_OUTPUT_BIN not set ' 175 "in build directory's ({}) .config". 176 format(build_dir)) 177 formats.append('bin') 178 elif args.gen_bin is None and bin_exists: 179 formats.append('bin') 180 181 hex_exists = build_conf.getboolean('CONFIG_BUILD_OUTPUT_HEX') 182 if args.gen_hex: 183 self.check_force(hex_exists, 184 '--hex given but CONFIG_BUILD_OUTPUT_HEX not set ' 185 "in build directory's ({}) .config". 186 format(build_dir)) 187 formats.append('hex') 188 elif args.gen_hex is None and hex_exists: 189 formats.append('hex') 190 191 # Delegate to the signer. 192 if args.tool == 'imgtool': 193 if args.if_tool_available: 194 self.die('imgtool does not support --if-tool-available') 195 signer = ImgtoolSigner() 196 elif args.tool == 'rimage': 197 signer = RimageSigner() 198 # (Add support for other signers here in elif blocks) 199 else: 200 if args.tool is None: 201 self.die('one --tool is required') 202 else: 203 self.die(f'invalid tool: {args.tool}') 204 205 signer.sign(self, build_dir, build_conf, formats) 206 207 208class Signer(abc.ABC): 209 '''Common abstract superclass for signers. 210 211 To add support for a new tool, subclass this and add support for 212 it in the Sign.do_run() method.''' 213 214 @abc.abstractmethod 215 def sign(self, command, build_dir, build_conf, formats): 216 '''Abstract method to perform a signature; subclasses must implement. 217 218 :param command: the Sign instance 219 :param build_dir: the build directory 220 :param build_conf: BuildConfiguration for build directory 221 :param formats: list of formats to generate ('bin', 'hex') 222 ''' 223 224 225class ImgtoolSigner(Signer): 226 227 def sign(self, command, build_dir, build_conf, formats): 228 if not formats: 229 return 230 231 args = command.args 232 b = pathlib.Path(build_dir) 233 234 command.wrn("west sign using imgtool is deprecated and will be removed in a future release") 235 236 imgtool = self.find_imgtool(command, args) 237 # The vector table offset and application version are set in Kconfig: 238 appver = self.get_cfg(command, build_conf, 'CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION') 239 vtoff = self.get_cfg(command, build_conf, 'CONFIG_ROM_START_OFFSET') 240 # Flash device write alignment and the partition's slot size 241 # come from devicetree: 242 flash = self.edt_flash_node(command, b, args.quiet) 243 align, addr, size = self.edt_flash_params(command, flash) 244 245 if not build_conf.getboolean('CONFIG_BOOTLOADER_MCUBOOT'): 246 command.wrn("CONFIG_BOOTLOADER_MCUBOOT is not set to y in " 247 f"{build_conf.path}; this probably won't work") 248 249 kernel = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr') 250 251 if 'bin' in formats: 252 in_bin = b / 'zephyr' / f'{kernel}.bin' 253 if not in_bin.is_file(): 254 command.die(f"no unsigned .bin found at {in_bin}") 255 in_bin = os.fspath(in_bin) 256 else: 257 in_bin = None 258 if 'hex' in formats: 259 in_hex = b / 'zephyr' / f'{kernel}.hex' 260 if not in_hex.is_file(): 261 command.die(f"no unsigned .hex found at {in_hex}") 262 in_hex = os.fspath(in_hex) 263 else: 264 in_hex = None 265 266 if not args.quiet: 267 command.banner('image configuration:') 268 command.inf('partition offset: {0} (0x{0:x})'.format(addr)) 269 command.inf('partition size: {0} (0x{0:x})'.format(size)) 270 command.inf('rom start offset: {0} (0x{0:x})'.format(vtoff)) 271 272 # Base sign command. 273 sign_base = imgtool + ['sign', 274 '--version', str(appver), 275 '--align', str(align), 276 '--header-size', str(vtoff), 277 '--slot-size', str(size)] 278 sign_base.extend(args.tool_args) 279 280 if not args.quiet: 281 command.banner('signing binaries') 282 if in_bin: 283 out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin') 284 sign_bin = sign_base + [in_bin, out_bin] 285 if not args.quiet: 286 command.inf(f'unsigned bin: {in_bin}') 287 command.inf(f'signed bin: {out_bin}') 288 command.dbg(quote_sh_list(sign_bin)) 289 subprocess.check_call(sign_bin, stdout=subprocess.PIPE if args.quiet else None) 290 if in_hex: 291 out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex') 292 sign_hex = sign_base + [in_hex, out_hex] 293 if not args.quiet: 294 command.inf(f'unsigned hex: {in_hex}') 295 command.inf(f'signed hex: {out_hex}') 296 command.dbg(quote_sh_list(sign_hex)) 297 subprocess.check_call(sign_hex, stdout=subprocess.PIPE if args.quiet else None) 298 299 @staticmethod 300 def find_imgtool(cmd, args): 301 if args.tool_path: 302 imgtool = args.tool_path 303 if not os.path.isfile(imgtool): 304 cmd.die(f'--tool-path {imgtool}: no such file') 305 else: 306 imgtool = shutil.which('imgtool') or shutil.which('imgtool.py') 307 if not imgtool: 308 cmd.die('imgtool not found; either install it', 309 '(e.g. "pip3 install imgtool") or provide --tool-path') 310 311 if platform.system() == 'Windows' and imgtool.endswith('.py'): 312 # Windows users may not be able to run .py files 313 # as executables in subprocesses, regardless of 314 # what the mode says. Always run imgtool as 315 # 'python path/to/imgtool.py' instead of 316 # 'path/to/imgtool.py' in these cases. 317 # https://github.com/zephyrproject-rtos/zephyr/issues/31876 318 return [sys.executable, imgtool] 319 320 return [imgtool] 321 322 @staticmethod 323 def get_cfg(command, build_conf, item): 324 try: 325 return build_conf[item] 326 except KeyError: 327 command.check_force( 328 False, "build .config is missing a {} value".format(item)) 329 return None 330 331 @staticmethod 332 def edt_flash_node(cmd, b, quiet=False): 333 # Get the EDT Node corresponding to the zephyr,flash chosen DT 334 # node; 'b' is the build directory as a pathlib object. 335 336 # Ensure the build directory has a compiled DTS file 337 # where we expect it to be. 338 dts = b / 'zephyr' / 'zephyr.dts' 339 if not quiet: 340 cmd.dbg('DTS file:', dts, level=Verbosity.DBG_MORE) 341 edt_pickle = b / 'zephyr' / 'edt.pickle' 342 if not edt_pickle.is_file(): 343 cmd.die("can't load devicetree; expected to find:", edt_pickle) 344 345 # Load the devicetree. 346 with open(edt_pickle, 'rb') as f: 347 edt = pickle.load(f) 348 349 # By convention, the zephyr,flash chosen node contains the 350 # partition information about the zephyr image to sign. 351 flash = edt.chosen_node('zephyr,flash') 352 if not flash: 353 cmd.die('devicetree has no chosen zephyr,flash node;', 354 "can't infer flash write block or slot0_partition slot sizes") 355 356 return flash 357 358 @staticmethod 359 def edt_flash_params(cmd, flash): 360 # Get the flash device's write alignment and offset from the 361 # slot0_partition and the size from slot1_partition , out of the 362 # build directory's devicetree. slot1_partition size is used, 363 # when available, because in swap-move mode it can be one sector 364 # smaller. When not available, fallback to slot0_partition (single slot dfu). 365 366 # The node must have a "partitions" child node, which in turn 367 # must have child nodes with label slot0_partition and may have a child node 368 # with label slot1_partition. By convention, the slots for consumption by 369 # imgtool are linked into these partitions. 370 if 'partitions' not in flash.children: 371 cmd.die("DT zephyr,flash chosen node has no partitions,", 372 "can't find partitions for MCUboot slots") 373 374 partitions = flash.children['partitions'] 375 slots = { 376 label: node for node in partitions.children.values() 377 for label in node.labels 378 if label in set(['slot0_partition', 'slot1_partition']) 379 } 380 381 if 'slot0_partition' not in slots: 382 cmd.die("DT zephyr,flash chosen node has no slot0_partition partition,", 383 "can't determine its address") 384 385 # Die on missing or zero alignment or slot_size. 386 if "write-block-size" not in flash.props: 387 cmd.die('DT zephyr,flash node has no write-block-size;', 388 "can't determine imgtool write alignment") 389 align = flash.props['write-block-size'].val 390 if align == 0: 391 cmd.die('expected nonzero flash alignment, but got ' 392 'DT flash device write-block-size {}'.format(align)) 393 394 # The partitions node, and its subnode, must provide 395 # the size of slot1_partition or slot0_partition partition via the regs property. 396 slot_key = 'slot1_partition' if 'slot1_partition' in slots else 'slot0_partition' 397 if not slots[slot_key].regs: 398 cmd.die(f'{slot_key} flash partition has no regs property;', 399 "can't determine size of slot") 400 401 # always use addr of slot0_partition, which is where slots are run 402 addr = slots['slot0_partition'].regs[0].addr 403 404 size = slots[slot_key].regs[0].size 405 if size == 0: 406 cmd.die('expected nonzero slot size for {}'.format(slot_key)) 407 408 return (align, addr, size) 409 410class RimageSigner(Signer): 411 412 def rimage_config_dir(self): 413 'Returns the rimage/config/ directory with the highest precedence' 414 args = self.command.args 415 if args.tool_data: 416 conf_dir = pathlib.Path(args.tool_data) 417 elif self.cmake_cache.get('RIMAGE_CONFIG_PATH'): 418 conf_dir = pathlib.Path(self.cmake_cache['RIMAGE_CONFIG_PATH']) 419 else: 420 conf_dir = self.sof_src_dir / 'tools' / 'rimage' / 'config' 421 self.command.dbg(f'rimage config directory={conf_dir}') 422 return conf_dir 423 424 def generate_uuid_registry(self): 425 'Runs the uuid-registry.h generator script' 426 427 generate_cmd = [sys.executable, str(self.sof_src_dir / 'scripts' / 'gen-uuid-reg.py'), 428 str(self.sof_src_dir / 'uuid-registry.txt'), 429 str(pathlib.Path('zephyr') / 'include' / 'generated' / 'uuid-registry.h') 430 ] 431 432 self.command.inf(quote_sh_list(generate_cmd)) 433 subprocess.run(generate_cmd, check=True, cwd=self.build_dir) 434 435 def preprocess_toml(self, config_dir, toml_basename, subdir): 436 'Runs the C pre-processor on config_dir/toml_basename.h' 437 438 compiler_path = self.cmake_cache.get("CMAKE_C_COMPILER") 439 preproc_cmd = [compiler_path, '-E', str(config_dir / (toml_basename + '.h'))] 440 # -P removes line markers to keep the .toml output reproducible. To 441 # trace #includes, temporarily comment out '-P' (-f*-prefix-map 442 # unfortunately don't seem to make any difference here and they're 443 # gcc-specific) 444 preproc_cmd += ['-P'] 445 446 # "REM" escapes _leading_ '#' characters from cpp and allows 447 # such comments to be preserved in generated/*.toml files: 448 # 449 # REM # my comment... 450 # 451 # Note _trailing_ '#' characters and comments are ignored by cpp 452 # and don't need any REM trick. 453 preproc_cmd += ['-DREM='] 454 455 preproc_cmd += ['-I', str(self.sof_src_dir / 'src')] 456 preproc_cmd += ['-imacros', 457 str(pathlib.Path('zephyr') / 'include' / 'generated' / 'zephyr' / 'autoconf.h')] 458 preproc_cmd += ['-imacros', 459 str(pathlib.Path('zephyr') / 'include' / 'generated' / 'uuid-registry.h')] 460 461 # Need to preprocess the TOML file twice: once with 462 # LLEXT_FORCE_ALL_MODULAR defined and once without it 463 full_preproc_cmd = preproc_cmd + ['-o', str(subdir / 'rimage_config_full.toml'), '-DLLEXT_FORCE_ALL_MODULAR'] 464 preproc_cmd += ['-o', str(subdir / 'rimage_config.toml')] 465 self.command.inf(quote_sh_list(preproc_cmd)) 466 subprocess.run(preproc_cmd, check=True, cwd=self.build_dir) 467 subprocess.run(full_preproc_cmd, check=True, cwd=self.build_dir) 468 469 def sign(self, command, build_dir, build_conf, formats): 470 self.command = command 471 args = command.args 472 473 b = pathlib.Path(build_dir) 474 self.build_dir = b 475 cache = CMakeCache.from_build_dir(build_dir) 476 self.cmake_cache = cache 477 478 # Warning: RIMAGE_TARGET in Zephyr is a duplicate of 479 # CONFIG_RIMAGE_SIGNING_SCHEMA in SOF. 480 target = cache.get('RIMAGE_TARGET') 481 482 if not target: 483 msg = 'rimage target not defined in board.cmake' 484 if args.if_tool_available: 485 command.inf(msg) 486 sys.exit(0) 487 else: 488 command.die(msg) 489 490 kernel_name = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr') 491 492 bootloader = None 493 cold = None 494 kernel = str(b / 'zephyr' / f'{kernel_name}.elf') 495 out_bin = str(b / 'zephyr' / f'{kernel_name}.ri') 496 out_xman = str(b / 'zephyr' / f'{kernel_name}.ri.xman') 497 out_tmp = str(b / 'zephyr' / f'{kernel_name}.rix') 498 499 # Intel platforms generate a "boot.mod" and "main.mod" as 500 # separate intermediates to use. Other platforms just use 501 # zephyr.elf directly. 502 if os.path.exists(str(b / 'zephyr' / 'boot.mod')): 503 bootloader = str(b / 'zephyr' / 'boot.mod') 504 if os.path.exists(str(b / 'zephyr' / 'cold.mod')): 505 cold = str(b / 'zephyr' / 'cold.mod') 506 with open(cold, 'rb') as f_cold: 507 elf = ELFFile(f_cold) 508 if elf.get_section_by_name('.cold') is None: 509 cold = None 510 if os.path.exists(str(b / 'zephyr' / 'main.mod')): 511 kernel = str(b / 'zephyr' / 'main.mod') 512 513 # Clean any stale output. This is especially important when using --if-tool-available 514 # (but not just) 515 for o in [ out_bin, out_xman, out_tmp ]: 516 pathlib.Path(o).unlink(missing_ok=True) 517 518 tool_path = ( 519 args.tool_path if args.tool_path else 520 command.config_get('rimage.path', None) 521 ) 522 err_prefix = '--tool-path' if args.tool_path else 'west config' 523 524 if tool_path: 525 command.check_force(shutil.which(tool_path), 526 f'{err_prefix} {tool_path}: not an executable') 527 else: 528 tool_path = shutil.which('rimage') 529 if not tool_path: 530 err_msg = 'rimage not found; either install it or provide --tool-path' 531 if args.if_tool_available: 532 command.wrn(err_msg) 533 command.wrn('zephyr binary _not_ signed!') 534 return 535 else: 536 command.die(err_msg) 537 538 #### -c sof/rimage/config/signing_schema.toml #### 539 540 if not args.quiet: 541 command.inf('Signing with tool {}'.format(tool_path)) 542 543 try: 544 sof_proj = command.manifest.get_projects(['sof'], allow_paths=False) 545 sof_src_dir = pathlib.Path(sof_proj[0].abspath) 546 except ValueError: # sof is the manifest 547 sof_src_dir = pathlib.Path(manifest.manifest_path()).parent 548 549 self.sof_src_dir = sof_src_dir 550 551 552 command.inf('Signing for SOC target ' + target) 553 554 # FIXME: deprecate --no-manifest and replace it with a much 555 # simpler and more direct `-- -e` which the user can _already_ 556 # pass today! With unclear consequences right now... 557 if '--no-manifest' in args.tool_args: 558 no_manifest = True 559 args.tool_args.remove('--no-manifest') 560 else: 561 no_manifest = False 562 563 # Non-SOF build does not have extended manifest data for 564 # rimage to process, which might result in rimage error. 565 # So skip it when not doing SOF builds. 566 is_sof_build = build_conf.getboolean('CONFIG_SOF') 567 if not is_sof_build: 568 no_manifest = True 569 self.generate_uuid_registry() 570 571 if no_manifest: 572 extra_ri_args = [ ] 573 else: 574 extra_ri_args = ['-e'] 575 576 sign_base = [tool_path] 577 578 # Align rimage verbosity. 579 # Sub-command arg 'west sign -q' takes precedence over west '-v' 580 if not args.quiet and args.verbose: 581 sign_base += ['-v'] * args.verbose 582 583 # Order is important 584 components = [ ] if bootloader is None else [ bootloader ] 585 if cold is not None: 586 components += [ cold ] 587 components += [ kernel ] 588 589 sign_config_extra_args = command.config_get_words('rimage.extra-args', []) 590 591 if '-k' not in sign_config_extra_args + args.tool_args: 592 # rimage requires a key argument even when it does not sign 593 cmake_default_key = cache.get('RIMAGE_SIGN_KEY', 'key placeholder from sign.py') 594 extra_ri_args += [ '-k', str(sof_src_dir / 'keys' / cmake_default_key) ] 595 596 if args.tool_data and '-c' in args.tool_args: 597 command.wrn('--tool-data ' + args.tool_data + ' ignored! Overridden by: -- -c ... ') 598 599 if '-c' not in sign_config_extra_args + args.tool_args: 600 conf_dir = self.rimage_config_dir() 601 toml_basename = target + '.toml' 602 if ((conf_dir / toml_basename).exists() and 603 (conf_dir / (toml_basename + '.h')).exists()): 604 command.die(f"Cannot have both {toml_basename + '.h'} and {toml_basename} in {conf_dir}") 605 606 if (conf_dir / (toml_basename + '.h')).exists(): 607 generated_subdir = pathlib.Path('zephyr') / 'misc' / 'generated' 608 self.preprocess_toml(conf_dir, toml_basename, generated_subdir) 609 extra_ri_args += ['-c', str(b / generated_subdir / 'rimage_config.toml')] 610 else: 611 toml_dir = conf_dir 612 extra_ri_args += ['-c', str(toml_dir / toml_basename)] 613 614 # Warning: while not officially supported (yet?), the rimage --option that is last 615 # on the command line currently wins in case of duplicate options. So pay 616 # attention to the _args order below. 617 sign_base += (['-o', out_bin] + sign_config_extra_args + 618 extra_ri_args + args.tool_args + components) 619 620 command.inf(quote_sh_list(sign_base)) 621 subprocess.check_call(sign_base) 622 623 if no_manifest: 624 filenames = [out_bin] 625 else: 626 filenames = [out_xman, out_bin] 627 if not args.quiet: 628 command.inf('Prefixing ' + out_bin + ' with manifest ' + out_xman) 629 with open(out_tmp, 'wb') as outfile: 630 for fname in filenames: 631 with open(fname, 'rb') as infile: 632 outfile.write(infile.read()) 633 634 os.remove(out_bin) 635 os.rename(out_tmp, out_bin) 636