1#!/usr/bin/env python3 2# 3# Copyright (c) 2013, Intel Corporation. 4# 5# SPDX-License-Identifier: GPL-2.0-only 6# 7# DESCRIPTION 'wic' is the OpenEmbedded Image Creator that users can 8# use to generate bootable images. Invoking it without any arguments 9# will display help screens for the 'wic' command and list the 10# available 'wic' subcommands. Invoking a subcommand without any 11# arguments will likewise display help screens for the specified 12# subcommand. Please use that interface for detailed help. 13# 14# AUTHORS 15# Tom Zanussi <tom.zanussi (at] linux.intel.com> 16# 17__version__ = "0.2.0" 18 19# Python Standard Library modules 20import os 21import sys 22import argparse 23import logging 24import subprocess 25 26from collections import namedtuple 27from distutils import spawn 28 29# External modules 30scripts_path = os.path.dirname(os.path.realpath(__file__)) 31lib_path = scripts_path + '/lib' 32sys.path.insert(0, lib_path) 33import scriptpath 34scriptpath.add_oe_lib_path() 35 36# Check whether wic is running within eSDK environment 37sdkroot = scripts_path 38if os.environ.get('SDKTARGETSYSROOT'): 39 while sdkroot != '' and sdkroot != os.sep: 40 if os.path.exists(os.path.join(sdkroot, '.devtoolbase')): 41 # Set BUILDDIR for wic to work within eSDK 42 os.environ['BUILDDIR'] = sdkroot 43 # .devtoolbase only exists within eSDK 44 # If found, initialize bitbake path for eSDK environment and append to PATH 45 sdkroot = os.path.join(os.path.dirname(scripts_path), 'bitbake', 'bin') 46 os.environ['PATH'] += ":" + sdkroot 47 break 48 sdkroot = os.path.dirname(sdkroot) 49 50bitbake_exe = spawn.find_executable('bitbake') 51if bitbake_exe: 52 bitbake_path = scriptpath.add_bitbake_lib_path() 53 import bb 54 55from wic import WicError 56from wic.misc import get_bitbake_var, BB_VARS 57from wic import engine 58from wic import help as hlp 59 60 61def wic_logger(): 62 """Create and convfigure wic logger.""" 63 logger = logging.getLogger('wic') 64 logger.setLevel(logging.INFO) 65 66 handler = logging.StreamHandler() 67 68 formatter = logging.Formatter('%(levelname)s: %(message)s') 69 handler.setFormatter(formatter) 70 71 logger.addHandler(handler) 72 73 return logger 74 75logger = wic_logger() 76 77def rootfs_dir_to_args(krootfs_dir): 78 """ 79 Get a rootfs_dir dict and serialize to string 80 """ 81 rootfs_dir = '' 82 for key, val in krootfs_dir.items(): 83 rootfs_dir += ' ' 84 rootfs_dir += '='.join([key, val]) 85 return rootfs_dir.strip() 86 87 88class RootfsArgAction(argparse.Action): 89 def __init__(self, **kwargs): 90 super().__init__(**kwargs) 91 92 def __call__(self, parser, namespace, value, option_string=None): 93 if not "rootfs_dir" in vars(namespace) or \ 94 not type(namespace.__dict__['rootfs_dir']) is dict: 95 namespace.__dict__['rootfs_dir'] = {} 96 97 if '=' in value: 98 (key, rootfs_dir) = value.split('=') 99 else: 100 key = 'ROOTFS_DIR' 101 rootfs_dir = value 102 103 namespace.__dict__['rootfs_dir'][key] = rootfs_dir 104 105 106def wic_create_subcommand(options, usage_str): 107 """ 108 Command-line handling for image creation. The real work is done 109 by image.engine.wic_create() 110 """ 111 if options.build_rootfs and not bitbake_exe: 112 raise WicError("Can't build rootfs as bitbake is not in the $PATH") 113 114 if not options.image_name: 115 missed = [] 116 for val, opt in [(options.rootfs_dir, 'rootfs-dir'), 117 (options.bootimg_dir, 'bootimg-dir'), 118 (options.kernel_dir, 'kernel-dir'), 119 (options.native_sysroot, 'native-sysroot')]: 120 if not val: 121 missed.append(opt) 122 if missed: 123 raise WicError("The following build artifacts are not specified: %s" % 124 ", ".join(missed)) 125 126 if options.image_name: 127 BB_VARS.default_image = options.image_name 128 else: 129 options.build_check = False 130 131 if options.vars_dir: 132 BB_VARS.vars_dir = options.vars_dir 133 134 if options.build_check and not engine.verify_build_env(): 135 raise WicError("Couldn't verify build environment, exiting") 136 137 if options.debug: 138 logger.setLevel(logging.DEBUG) 139 140 if options.image_name: 141 if options.build_rootfs: 142 argv = ["bitbake", options.image_name] 143 if options.debug: 144 argv.append("--debug") 145 146 logger.info("Building rootfs...\n") 147 subprocess.check_call(argv) 148 149 rootfs_dir = get_bitbake_var("IMAGE_ROOTFS", options.image_name) 150 kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE", options.image_name) 151 bootimg_dir = get_bitbake_var("STAGING_DATADIR", options.image_name) 152 153 native_sysroot = options.native_sysroot 154 if options.vars_dir and not native_sysroot: 155 native_sysroot = get_bitbake_var("RECIPE_SYSROOT_NATIVE", options.image_name) 156 else: 157 if options.build_rootfs: 158 raise WicError("Image name is not specified, exiting. " 159 "(Use -e/--image-name to specify it)") 160 native_sysroot = options.native_sysroot 161 162 if not options.vars_dir and (not native_sysroot or not os.path.isdir(native_sysroot)): 163 logger.info("Building wic-tools...\n") 164 subprocess.check_call(["bitbake", "wic-tools"]) 165 native_sysroot = get_bitbake_var("RECIPE_SYSROOT_NATIVE", "wic-tools") 166 167 if not native_sysroot: 168 raise WicError("Unable to find the location of the native tools sysroot") 169 170 wks_file = options.wks_file 171 172 if not wks_file.endswith(".wks"): 173 wks_file = engine.find_canned_image(scripts_path, wks_file) 174 if not wks_file: 175 raise WicError("No image named %s found, exiting. (Use 'wic list images' " 176 "to list available images, or specify a fully-qualified OE " 177 "kickstart (.wks) filename)" % options.wks_file) 178 179 if not options.image_name: 180 rootfs_dir = '' 181 if 'ROOTFS_DIR' in options.rootfs_dir: 182 rootfs_dir = options.rootfs_dir['ROOTFS_DIR'] 183 bootimg_dir = options.bootimg_dir 184 kernel_dir = options.kernel_dir 185 native_sysroot = options.native_sysroot 186 if rootfs_dir and not os.path.isdir(rootfs_dir): 187 raise WicError("--rootfs-dir (-r) not found, exiting") 188 if not os.path.isdir(bootimg_dir): 189 raise WicError("--bootimg-dir (-b) not found, exiting") 190 if not os.path.isdir(kernel_dir): 191 raise WicError("--kernel-dir (-k) not found, exiting") 192 if not os.path.isdir(native_sysroot): 193 raise WicError("--native-sysroot (-n) not found, exiting") 194 else: 195 not_found = not_found_dir = "" 196 if not os.path.isdir(rootfs_dir): 197 (not_found, not_found_dir) = ("rootfs-dir", rootfs_dir) 198 elif not os.path.isdir(kernel_dir): 199 (not_found, not_found_dir) = ("kernel-dir", kernel_dir) 200 elif not os.path.isdir(native_sysroot): 201 (not_found, not_found_dir) = ("native-sysroot", native_sysroot) 202 if not_found: 203 if not not_found_dir: 204 not_found_dir = "Completely missing artifact - wrong image (.wks) used?" 205 logger.info("Build artifacts not found, exiting.") 206 logger.info(" (Please check that the build artifacts for the machine") 207 logger.info(" selected in local.conf actually exist and that they") 208 logger.info(" are the correct artifacts for the image (.wks file)).\n") 209 raise WicError("The artifact that couldn't be found was %s:\n %s", not_found, not_found_dir) 210 211 krootfs_dir = options.rootfs_dir 212 if krootfs_dir is None: 213 krootfs_dir = {} 214 krootfs_dir['ROOTFS_DIR'] = rootfs_dir 215 216 rootfs_dir = rootfs_dir_to_args(krootfs_dir) 217 218 logger.info("Creating image(s)...\n") 219 engine.wic_create(wks_file, rootfs_dir, bootimg_dir, kernel_dir, 220 native_sysroot, options) 221 222 223def wic_list_subcommand(args, usage_str): 224 """ 225 Command-line handling for listing available images. 226 The real work is done by image.engine.wic_list() 227 """ 228 if not engine.wic_list(args, scripts_path): 229 raise WicError("Bad list arguments, exiting") 230 231 232def wic_ls_subcommand(args, usage_str): 233 """ 234 Command-line handling for list content of images. 235 The real work is done by engine.wic_ls() 236 """ 237 engine.wic_ls(args, args.native_sysroot) 238 239def wic_cp_subcommand(args, usage_str): 240 """ 241 Command-line handling for copying files/dirs to images. 242 The real work is done by engine.wic_cp() 243 """ 244 engine.wic_cp(args, args.native_sysroot) 245 246def wic_rm_subcommand(args, usage_str): 247 """ 248 Command-line handling for removing files/dirs from images. 249 The real work is done by engine.wic_rm() 250 """ 251 engine.wic_rm(args, args.native_sysroot) 252 253def wic_write_subcommand(args, usage_str): 254 """ 255 Command-line handling for writing images. 256 The real work is done by engine.wic_write() 257 """ 258 engine.wic_write(args, args.native_sysroot) 259 260def wic_help_subcommand(args, usage_str): 261 """ 262 Command-line handling for help subcommand to keep the current 263 structure of the function definitions. 264 """ 265 pass 266 267 268def wic_help_topic_subcommand(usage_str, help_str): 269 """ 270 Display function for help 'sub-subcommands'. 271 """ 272 print(help_str) 273 return 274 275 276wic_help_topic_usage = """ 277""" 278 279helptopics = { 280 "plugins": [wic_help_topic_subcommand, 281 wic_help_topic_usage, 282 hlp.wic_plugins_help], 283 "overview": [wic_help_topic_subcommand, 284 wic_help_topic_usage, 285 hlp.wic_overview_help], 286 "kickstart": [wic_help_topic_subcommand, 287 wic_help_topic_usage, 288 hlp.wic_kickstart_help], 289 "create": [wic_help_topic_subcommand, 290 wic_help_topic_usage, 291 hlp.wic_create_help], 292 "ls": [wic_help_topic_subcommand, 293 wic_help_topic_usage, 294 hlp.wic_ls_help], 295 "cp": [wic_help_topic_subcommand, 296 wic_help_topic_usage, 297 hlp.wic_cp_help], 298 "rm": [wic_help_topic_subcommand, 299 wic_help_topic_usage, 300 hlp.wic_rm_help], 301 "write": [wic_help_topic_subcommand, 302 wic_help_topic_usage, 303 hlp.wic_write_help], 304 "list": [wic_help_topic_subcommand, 305 wic_help_topic_usage, 306 hlp.wic_list_help] 307} 308 309 310def wic_init_parser_create(subparser): 311 subparser.add_argument("wks_file") 312 313 subparser.add_argument("-o", "--outdir", dest="outdir", default='.', 314 help="name of directory to create image in") 315 subparser.add_argument("-w", "--workdir", 316 help="temporary workdir to use for intermediate files") 317 subparser.add_argument("-e", "--image-name", dest="image_name", 318 help="name of the image to use the artifacts from " 319 "e.g. core-image-sato") 320 subparser.add_argument("-r", "--rootfs-dir", action=RootfsArgAction, 321 help="path to the /rootfs dir to use as the " 322 ".wks rootfs source") 323 subparser.add_argument("-b", "--bootimg-dir", dest="bootimg_dir", 324 help="path to the dir containing the boot artifacts " 325 "(e.g. /EFI or /syslinux dirs) to use as the " 326 ".wks bootimg source") 327 subparser.add_argument("-k", "--kernel-dir", dest="kernel_dir", 328 help="path to the dir containing the kernel to use " 329 "in the .wks bootimg") 330 subparser.add_argument("-n", "--native-sysroot", dest="native_sysroot", 331 help="path to the native sysroot containing the tools " 332 "to use to build the image") 333 subparser.add_argument("-s", "--skip-build-check", dest="build_check", 334 action="store_false", default=True, help="skip the build check") 335 subparser.add_argument("-f", "--build-rootfs", action="store_true", help="build rootfs") 336 subparser.add_argument("-c", "--compress-with", choices=("gzip", "bzip2", "xz"), 337 dest='compressor', 338 help="compress image with specified compressor") 339 subparser.add_argument("-m", "--bmap", action="store_true", help="generate .bmap") 340 subparser.add_argument("--no-fstab-update" ,action="store_true", 341 help="Do not change fstab file.") 342 subparser.add_argument("-v", "--vars", dest='vars_dir', 343 help="directory with <image>.env files that store " 344 "bitbake variables") 345 subparser.add_argument("-D", "--debug", dest="debug", action="store_true", 346 default=False, help="output debug information") 347 subparser.add_argument("-i", "--imager", dest="imager", 348 default="direct", help="the wic imager plugin") 349 return 350 351 352def wic_init_parser_list(subparser): 353 subparser.add_argument("list_type", 354 help="can be 'images' or 'source-plugins' " 355 "to obtain a list. " 356 "If value is a valid .wks image file") 357 subparser.add_argument("help_for", default=[], nargs='*', 358 help="If 'list_type' is a valid .wks image file " 359 "this value can be 'help' to show the help information " 360 "defined inside the .wks file") 361 return 362 363def imgtype(arg): 364 """ 365 Custom type for ArgumentParser 366 Converts path spec to named tuple: (image, partition, path) 367 """ 368 image = arg 369 part = path = None 370 if ':' in image: 371 image, part = image.split(':') 372 if '/' in part: 373 part, path = part.split('/', 1) 374 if not path: 375 path = '/' 376 377 if not os.path.isfile(image): 378 err = "%s is not a regular file or symlink" % image 379 raise argparse.ArgumentTypeError(err) 380 381 return namedtuple('ImgType', 'image part path')(image, part, path) 382 383def wic_init_parser_ls(subparser): 384 subparser.add_argument("path", type=imgtype, 385 help="image spec: <image>[:<vfat partition>[<path>]]") 386 subparser.add_argument("-n", "--native-sysroot", 387 help="path to the native sysroot containing the tools") 388 389def imgpathtype(arg): 390 img = imgtype(arg) 391 if img.part is None: 392 raise argparse.ArgumentTypeError("partition number is not specified") 393 return img 394 395def wic_init_parser_cp(subparser): 396 subparser.add_argument("src", 397 help="image spec: <image>:<vfat partition>[<path>] or <file>") 398 subparser.add_argument("dest", 399 help="image spec: <image>:<vfat partition>[<path>] or <file>") 400 subparser.add_argument("-n", "--native-sysroot", 401 help="path to the native sysroot containing the tools") 402 403def wic_init_parser_rm(subparser): 404 subparser.add_argument("path", type=imgpathtype, 405 help="path: <image>:<vfat partition><path>") 406 subparser.add_argument("-n", "--native-sysroot", 407 help="path to the native sysroot containing the tools") 408 subparser.add_argument("-r", dest="recursive_delete", action="store_true", default=False, 409 help="remove directories and their contents recursively, " 410 " this only applies to ext* partition") 411 412def expandtype(rules): 413 """ 414 Custom type for ArgumentParser 415 Converts expand rules to the dictionary {<partition>: size} 416 """ 417 if rules == 'auto': 418 return {} 419 result = {} 420 for rule in rules.split(','): 421 try: 422 part, size = rule.split(':') 423 except ValueError: 424 raise argparse.ArgumentTypeError("Incorrect rule format: %s" % rule) 425 426 if not part.isdigit(): 427 raise argparse.ArgumentTypeError("Rule '%s': partition number must be integer" % rule) 428 429 # validate size 430 multiplier = 1 431 for suffix, mult in [('K', 1024), ('M', 1024 * 1024), ('G', 1024 * 1024 * 1024)]: 432 if size.upper().endswith(suffix): 433 multiplier = mult 434 size = size[:-1] 435 break 436 if not size.isdigit(): 437 raise argparse.ArgumentTypeError("Rule '%s': size must be integer" % rule) 438 439 result[int(part)] = int(size) * multiplier 440 441 return result 442 443def wic_init_parser_write(subparser): 444 subparser.add_argument("image", 445 help="path to the wic image") 446 subparser.add_argument("target", 447 help="target file or device") 448 subparser.add_argument("-e", "--expand", type=expandtype, 449 help="expand rules: auto or <partition>:<size>[,<partition>:<size>]") 450 subparser.add_argument("-n", "--native-sysroot", 451 help="path to the native sysroot containing the tools") 452 453def wic_init_parser_help(subparser): 454 helpparsers = subparser.add_subparsers(dest='help_topic', help=hlp.wic_usage) 455 for helptopic in helptopics: 456 helpparsers.add_parser(helptopic, help=helptopics[helptopic][2]) 457 return 458 459 460subcommands = { 461 "create": [wic_create_subcommand, 462 hlp.wic_create_usage, 463 hlp.wic_create_help, 464 wic_init_parser_create], 465 "list": [wic_list_subcommand, 466 hlp.wic_list_usage, 467 hlp.wic_list_help, 468 wic_init_parser_list], 469 "ls": [wic_ls_subcommand, 470 hlp.wic_ls_usage, 471 hlp.wic_ls_help, 472 wic_init_parser_ls], 473 "cp": [wic_cp_subcommand, 474 hlp.wic_cp_usage, 475 hlp.wic_cp_help, 476 wic_init_parser_cp], 477 "rm": [wic_rm_subcommand, 478 hlp.wic_rm_usage, 479 hlp.wic_rm_help, 480 wic_init_parser_rm], 481 "write": [wic_write_subcommand, 482 hlp.wic_write_usage, 483 hlp.wic_write_help, 484 wic_init_parser_write], 485 "help": [wic_help_subcommand, 486 wic_help_topic_usage, 487 hlp.wic_help_help, 488 wic_init_parser_help] 489} 490 491 492def init_parser(parser): 493 parser.add_argument("--version", action="version", 494 version="%(prog)s {version}".format(version=__version__)) 495 parser.add_argument("-D", "--debug", dest="debug", action="store_true", 496 default=False, help="output debug information") 497 498 subparsers = parser.add_subparsers(dest='command', help=hlp.wic_usage) 499 for subcmd in subcommands: 500 subparser = subparsers.add_parser(subcmd, help=subcommands[subcmd][2]) 501 subcommands[subcmd][3](subparser) 502 503class WicArgumentParser(argparse.ArgumentParser): 504 def format_help(self): 505 return hlp.wic_help 506 507def main(argv): 508 parser = WicArgumentParser( 509 description="wic version %s" % __version__) 510 511 init_parser(parser) 512 513 args = parser.parse_args(argv) 514 515 if args.debug: 516 logger.setLevel(logging.DEBUG) 517 518 if "command" in vars(args): 519 if args.command == "help": 520 if args.help_topic is None: 521 parser.print_help() 522 elif args.help_topic in helptopics: 523 hlpt = helptopics[args.help_topic] 524 hlpt[0](hlpt[1], hlpt[2]) 525 return 0 526 527 # validate wic cp src and dest parameter to identify which one of it is 528 # image and cast it into imgtype 529 if args.command == "cp": 530 if ":" in args.dest: 531 args.dest = imgtype(args.dest) 532 elif ":" in args.src: 533 args.src = imgtype(args.src) 534 else: 535 raise argparse.ArgumentTypeError("no image or partition number specified.") 536 537 return hlp.invoke_subcommand(args, parser, hlp.wic_help_usage, subcommands) 538 539 540if __name__ == "__main__": 541 try: 542 sys.exit(main(sys.argv[1:])) 543 except WicError as err: 544 print() 545 logger.error(err) 546 sys.exit(1) 547