diff options
Diffstat (limited to 'scripts/wic')
-rwxr-xr-x | scripts/wic | 554 |
1 files changed, 391 insertions, 163 deletions
diff --git a/scripts/wic b/scripts/wic index fe2c33f0e7..06e0b48db0 100755 --- a/scripts/wic +++ b/scripts/wic @@ -1,22 +1,8 @@ #!/usr/bin/env python3 -# ex:ts=4:sw=4:sts=4:et -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # # Copyright (c) 2013, Intel Corporation. -# All rights reserved. # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# SPDX-License-Identifier: GPL-2.0-only # # DESCRIPTION 'wic' is the OpenEmbedded Image Creator that users can # use to generate bootable images. Invoking it without any arguments @@ -33,29 +19,61 @@ __version__ = "0.2.0" # Python Standard Library modules import os import sys -import optparse +import argparse import logging -from distutils import spawn +import subprocess +import shutil + +from collections import namedtuple # External modules -scripts_path = os.path.abspath(os.path.dirname(__file__)) +scripts_path = os.path.dirname(os.path.realpath(__file__)) lib_path = scripts_path + '/lib' sys.path.insert(0, lib_path) - -bitbake_exe = spawn.find_executable('bitbake') +import scriptpath +scriptpath.add_oe_lib_path() + +# Check whether wic is running within eSDK environment +sdkroot = scripts_path +if os.environ.get('SDKTARGETSYSROOT'): + while sdkroot != '' and sdkroot != os.sep: + if os.path.exists(os.path.join(sdkroot, '.devtoolbase')): + # Set BUILDDIR for wic to work within eSDK + os.environ['BUILDDIR'] = sdkroot + # .devtoolbase only exists within eSDK + # If found, initialize bitbake path for eSDK environment and append to PATH + sdkroot = os.path.join(os.path.dirname(scripts_path), 'bitbake', 'bin') + os.environ['PATH'] += ":" + sdkroot + break + sdkroot = os.path.dirname(sdkroot) + +bitbake_exe = shutil.which('bitbake') if bitbake_exe: - bitbake_path = os.path.join(os.path.dirname(bitbake_exe), '../lib') - sys.path.insert(0, bitbake_path) - from bb import cookerdata - from bb.main import bitbake_main, BitBakeConfigParameters -else: - bitbake_main = None - -from wic.utils.oe.misc import get_bitbake_var, BB_VARS -from wic.utils.errors import WicError + bitbake_path = scriptpath.add_bitbake_lib_path() + import bb + +from wic import WicError +from wic.misc import get_bitbake_var, BB_VARS from wic import engine from wic import help as hlp + +def wic_logger(): + """Create and convfigure wic logger.""" + logger = logging.getLogger('wic') + logger.setLevel(logging.INFO) + + handler = logging.StreamHandler() + + formatter = logging.Formatter('%(levelname)s: %(message)s') + handler.setFormatter(formatter) + + logger.addHandler(handler) + + return logger + +logger = wic_logger() + def rootfs_dir_to_args(krootfs_dir): """ Get a rootfs_dir dict and serialize to string @@ -66,70 +84,32 @@ def rootfs_dir_to_args(krootfs_dir): rootfs_dir += '='.join([key, val]) return rootfs_dir.strip() -def callback_rootfs_dir(option, opt, value, parser): - """ - Build a dict using --rootfs_dir connection=dir - """ - if not type(parser.values.rootfs_dir) is dict: - parser.values.rootfs_dir = dict() - if '=' in value: - (key, rootfs_dir) = value.split('=') - else: - key = 'ROOTFS_DIR' - rootfs_dir = value +class RootfsArgAction(argparse.Action): + def __init__(self, **kwargs): + super().__init__(**kwargs) - parser.values.rootfs_dir[key] = rootfs_dir + def __call__(self, parser, namespace, value, option_string=None): + if not "rootfs_dir" in vars(namespace) or \ + not type(namespace.__dict__['rootfs_dir']) is dict: + namespace.__dict__['rootfs_dir'] = {} -def wic_create_subcommand(args, usage_str): + if '=' in value: + (key, rootfs_dir) = value.split('=') + else: + key = 'ROOTFS_DIR' + rootfs_dir = value + + namespace.__dict__['rootfs_dir'][key] = rootfs_dir + + +def wic_create_subcommand(options, usage_str): """ Command-line handling for image creation. The real work is done by image.engine.wic_create() """ - parser = optparse.OptionParser(usage=usage_str) - - parser.add_option("-o", "--outdir", dest="outdir", - help="name of directory to create image in") - parser.add_option("-e", "--image-name", dest="image_name", - help="name of the image to use the artifacts from " - "e.g. core-image-sato") - parser.add_option("-r", "--rootfs-dir", dest="rootfs_dir", type="string", - action="callback", callback=callback_rootfs_dir, - help="path to the /rootfs dir to use as the " - ".wks rootfs source") - parser.add_option("-b", "--bootimg-dir", dest="bootimg_dir", - help="path to the dir containing the boot artifacts " - "(e.g. /EFI or /syslinux dirs) to use as the " - ".wks bootimg source") - parser.add_option("-k", "--kernel-dir", dest="kernel_dir", - help="path to the dir containing the kernel to use " - "in the .wks bootimg") - parser.add_option("-n", "--native-sysroot", dest="native_sysroot", - help="path to the native sysroot containing the tools " - "to use to build the image") - parser.add_option("-p", "--skip-build-check", dest="build_check", - action="store_false", default=True, help="skip the build check") - parser.add_option("-f", "--build-rootfs", action="store_true", help="build rootfs") - parser.add_option("-c", "--compress-with", choices=("gzip", "bzip2", "xz"), - dest='compressor', - help="compress image with specified compressor") - parser.add_option("-m", "--bmap", action="store_true", help="generate .bmap") - parser.add_option("-v", "--vars", dest='vars_dir', - help="directory with <image>.env files that store " - "bitbake variables") - parser.add_option("-D", "--debug", dest="debug", action="store_true", - default=False, help="output debug information") - - (options, args) = parser.parse_args(args) - - if len(args) != 1: - logging.error("Wrong number of arguments, exiting\n") - parser.print_help() - sys.exit(1) - - if options.build_rootfs and not bitbake_main: - logging.error("Can't build roofs as bitbake is not in the $PATH") - sys.exit(1) + if options.build_rootfs and not bitbake_exe: + raise WicError("Can't build rootfs as bitbake is not in the $PATH") if not options.image_name: missed = [] @@ -140,9 +120,8 @@ def wic_create_subcommand(args, usage_str): if not val: missed.append(opt) if missed: - print("The following build artifacts are not specified:") - print(" " + ", ".join(missed)) - sys.exit(1) + raise WicError("The following build artifacts are not specified: %s" % + ", ".join(missed)) if options.image_name: BB_VARS.default_image = options.image_name @@ -152,15 +131,11 @@ def wic_create_subcommand(args, usage_str): if options.vars_dir: BB_VARS.vars_dir = options.vars_dir - if options.build_check: - print("Checking basic build environment...") - if not engine.verify_build_env(): - print("Couldn't verify build environment, exiting\n") - sys.exit(1) - else: - print("Done.\n") + if options.build_check and not engine.verify_build_env(): + raise WicError("Couldn't verify build environment, exiting") - bootimg_dir = "" + if options.debug: + logger.setLevel(logging.DEBUG) if options.image_name: if options.build_rootfs: @@ -168,33 +143,41 @@ def wic_create_subcommand(args, usage_str): if options.debug: argv.append("--debug") - print("Building rootfs...\n") - if bitbake_main(BitBakeConfigParameters(argv), - cookerdata.CookerConfiguration()): - sys.exit(1) + logger.info("Building rootfs...\n") + subprocess.check_call(argv) rootfs_dir = get_bitbake_var("IMAGE_ROOTFS", options.image_name) kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE", options.image_name) - native_sysroot = get_bitbake_var("STAGING_DIR_NATIVE", - options.image_name) + bootimg_dir = get_bitbake_var("STAGING_DATADIR", options.image_name) + + native_sysroot = options.native_sysroot + if options.vars_dir and not native_sysroot: + native_sysroot = get_bitbake_var("RECIPE_SYSROOT_NATIVE", options.image_name) else: if options.build_rootfs: - print("Image name is not specified, exiting. (Use -e/--image-name to specify it)\n") - sys.exit(1) + raise WicError("Image name is not specified, exiting. " + "(Use -e/--image-name to specify it)") + native_sysroot = options.native_sysroot + + if options.kernel_dir: + kernel_dir = options.kernel_dir + + if not options.vars_dir and (not native_sysroot or not os.path.isdir(native_sysroot)): + logger.info("Building wic-tools...\n") + subprocess.check_call(["bitbake", "wic-tools"]) + native_sysroot = get_bitbake_var("RECIPE_SYSROOT_NATIVE", "wic-tools") - wks_file = args[0] + if not native_sysroot: + raise WicError("Unable to find the location of the native tools sysroot") + + wks_file = options.wks_file if not wks_file.endswith(".wks"): wks_file = engine.find_canned_image(scripts_path, wks_file) if not wks_file: - print("No image named %s found, exiting. (Use 'wic list images' "\ - "to list available images, or specify a fully-qualified OE "\ - "kickstart (.wks) filename)\n" % args[0]) - sys.exit(1) - - image_output_dir = "" - if options.outdir: - image_output_dir = options.outdir + raise WicError("No image named %s found, exiting. (Use 'wic list images' " + "to list available images, or specify a fully-qualified OE " + "kickstart (.wks) filename)" % options.wks_file) if not options.image_name: rootfs_dir = '' @@ -204,17 +187,13 @@ def wic_create_subcommand(args, usage_str): kernel_dir = options.kernel_dir native_sysroot = options.native_sysroot if rootfs_dir and not os.path.isdir(rootfs_dir): - print("--roofs-dir (-r) not found, exiting\n") - sys.exit(1) + raise WicError("--rootfs-dir (-r) not found, exiting") if not os.path.isdir(bootimg_dir): - print("--bootimg-dir (-b) not found, exiting\n") - sys.exit(1) + raise WicError("--bootimg-dir (-b) not found, exiting") if not os.path.isdir(kernel_dir): - print("--kernel-dir (-k) not found, exiting\n") - sys.exit(1) + raise WicError("--kernel-dir (-k) not found, exiting") if not os.path.isdir(native_sysroot): - print("--native-sysroot (-n) not found, exiting\n") - sys.exit(1) + raise WicError("--native-sysroot (-n) not found, exiting") else: not_found = not_found_dir = "" if not os.path.isdir(rootfs_dir): @@ -226,13 +205,11 @@ def wic_create_subcommand(args, usage_str): if not_found: if not not_found_dir: not_found_dir = "Completely missing artifact - wrong image (.wks) used?" - print("Build artifacts not found, exiting.") - print(" (Please check that the build artifacts for the machine") - print(" selected in local.conf actually exist and that they") - print(" are the correct artifacts for the image (.wks file)).\n") - print("The artifact that couldn't be found was %s:\n %s" % \ - (not_found, not_found_dir)) - sys.exit(1) + logger.info("Build artifacts not found, exiting.") + logger.info(" (Please check that the build artifacts for the machine") + logger.info(" selected in local.conf actually exist and that they") + logger.info(" are the correct artifacts for the image (.wks file)).\n") + raise WicError("The artifact that couldn't be found was %s:\n %s" % (not_found, not_found_dir)) krootfs_dir = options.rootfs_dir if krootfs_dir is None: @@ -241,10 +218,9 @@ def wic_create_subcommand(args, usage_str): rootfs_dir = rootfs_dir_to_args(krootfs_dir) - print("Creating image(s)...\n") + logger.info("Creating image(s)...\n") engine.wic_create(wks_file, rootfs_dir, bootimg_dir, kernel_dir, - native_sysroot, scripts_path, image_output_dir, - options.compressor, options.bmap, options.debug) + native_sysroot, options) def wic_list_subcommand(args, usage_str): @@ -252,64 +228,316 @@ def wic_list_subcommand(args, usage_str): Command-line handling for listing available images. The real work is done by image.engine.wic_list() """ - parser = optparse.OptionParser(usage=usage_str) - args = parser.parse_args(args)[1] - if not engine.wic_list(args, scripts_path): - logging.error("Bad list arguments, exiting\n") - parser.print_help() - sys.exit(1) + raise WicError("Bad list arguments, exiting") -def wic_help_topic_subcommand(args, usage_str): +def wic_ls_subcommand(args, usage_str): """ - Command-line handling for help-only 'subcommands'. This is - essentially a dummy command that doesn nothing but allow users to - use the existing subcommand infrastructure to display help on a - particular topic not attached to any particular subcommand. + Command-line handling for list content of images. + The real work is done by engine.wic_ls() + """ + engine.wic_ls(args, args.native_sysroot) + +def wic_cp_subcommand(args, usage_str): + """ + Command-line handling for copying files/dirs to images. + The real work is done by engine.wic_cp() + """ + engine.wic_cp(args, args.native_sysroot) + +def wic_rm_subcommand(args, usage_str): + """ + Command-line handling for removing files/dirs from images. + The real work is done by engine.wic_rm() + """ + engine.wic_rm(args, args.native_sysroot) + +def wic_write_subcommand(args, usage_str): + """ + Command-line handling for writing images. + The real work is done by engine.wic_write() + """ + engine.wic_write(args, args.native_sysroot) + +def wic_help_subcommand(args, usage_str): + """ + Command-line handling for help subcommand to keep the current + structure of the function definitions. """ pass +def wic_help_topic_subcommand(usage_str, help_str): + """ + Display function for help 'sub-subcommands'. + """ + print(help_str) + return + + wic_help_topic_usage = """ """ -subcommands = { - "create": [wic_create_subcommand, - hlp.wic_create_usage, - hlp.wic_create_help], - "list": [wic_list_subcommand, - hlp.wic_list_usage, - hlp.wic_list_help], +helptopics = { "plugins": [wic_help_topic_subcommand, wic_help_topic_usage, - hlp.get_wic_plugins_help], + hlp.wic_plugins_help], "overview": [wic_help_topic_subcommand, wic_help_topic_usage, hlp.wic_overview_help], "kickstart": [wic_help_topic_subcommand, wic_help_topic_usage, hlp.wic_kickstart_help], + "create": [wic_help_topic_subcommand, + wic_help_topic_usage, + hlp.wic_create_help], + "ls": [wic_help_topic_subcommand, + wic_help_topic_usage, + hlp.wic_ls_help], + "cp": [wic_help_topic_subcommand, + wic_help_topic_usage, + hlp.wic_cp_help], + "rm": [wic_help_topic_subcommand, + wic_help_topic_usage, + hlp.wic_rm_help], + "write": [wic_help_topic_subcommand, + wic_help_topic_usage, + hlp.wic_write_help], + "list": [wic_help_topic_subcommand, + wic_help_topic_usage, + hlp.wic_list_help] +} + + +def wic_init_parser_create(subparser): + subparser.add_argument("wks_file") + + subparser.add_argument("-o", "--outdir", dest="outdir", default='.', + help="name of directory to create image in") + subparser.add_argument("-w", "--workdir", + help="temporary workdir to use for intermediate files") + subparser.add_argument("-e", "--image-name", dest="image_name", + help="name of the image to use the artifacts from " + "e.g. core-image-sato") + subparser.add_argument("-r", "--rootfs-dir", action=RootfsArgAction, + help="path to the /rootfs dir to use as the " + ".wks rootfs source") + subparser.add_argument("-b", "--bootimg-dir", dest="bootimg_dir", + help="path to the dir containing the boot artifacts " + "(e.g. /EFI or /syslinux dirs) to use as the " + ".wks bootimg source") + subparser.add_argument("-k", "--kernel-dir", dest="kernel_dir", + help="path to the dir containing the kernel to use " + "in the .wks bootimg") + subparser.add_argument("-n", "--native-sysroot", dest="native_sysroot", + help="path to the native sysroot containing the tools " + "to use to build the image") + subparser.add_argument("-s", "--skip-build-check", dest="build_check", + action="store_false", default=True, help="skip the build check") + subparser.add_argument("-f", "--build-rootfs", action="store_true", help="build rootfs") + subparser.add_argument("-c", "--compress-with", choices=("gzip", "bzip2", "xz"), + dest='compressor', + help="compress image with specified compressor") + subparser.add_argument("-m", "--bmap", action="store_true", help="generate .bmap") + subparser.add_argument("--no-fstab-update" ,action="store_true", + help="Do not change fstab file.") + subparser.add_argument("-v", "--vars", dest='vars_dir', + help="directory with <image>.env files that store " + "bitbake variables") + subparser.add_argument("-D", "--debug", dest="debug", action="store_true", + default=False, help="output debug information") + subparser.add_argument("-i", "--imager", dest="imager", + default="direct", help="the wic imager plugin") + subparser.add_argument("--extra-space", type=int, dest="extra_space", + default=0, help="additional free disk space to add to the image") + return + + +def wic_init_parser_list(subparser): + subparser.add_argument("list_type", + help="can be 'images' or 'source-plugins' " + "to obtain a list. " + "If value is a valid .wks image file") + subparser.add_argument("help_for", default=[], nargs='*', + help="If 'list_type' is a valid .wks image file " + "this value can be 'help' to show the help information " + "defined inside the .wks file") + return + +def imgtype(arg): + """ + Custom type for ArgumentParser + Converts path spec to named tuple: (image, partition, path) + """ + image = arg + part = path = None + if ':' in image: + image, part = image.split(':') + if '/' in part: + part, path = part.split('/', 1) + if not path: + path = '/' + + if not os.path.isfile(image): + err = "%s is not a regular file or symlink" % image + raise argparse.ArgumentTypeError(err) + + return namedtuple('ImgType', 'image part path')(image, part, path) + +def wic_init_parser_ls(subparser): + subparser.add_argument("path", type=imgtype, + help="image spec: <image>[:<vfat partition>[<path>]]") + subparser.add_argument("-n", "--native-sysroot", + help="path to the native sysroot containing the tools") + +def imgpathtype(arg): + img = imgtype(arg) + if img.part is None: + raise argparse.ArgumentTypeError("partition number is not specified") + return img + +def wic_init_parser_cp(subparser): + subparser.add_argument("src", + help="image spec: <image>:<vfat partition>[<path>] or <file>") + subparser.add_argument("dest", + help="image spec: <image>:<vfat partition>[<path>] or <file>") + subparser.add_argument("-n", "--native-sysroot", + help="path to the native sysroot containing the tools") + +def wic_init_parser_rm(subparser): + subparser.add_argument("path", type=imgpathtype, + help="path: <image>:<vfat partition><path>") + subparser.add_argument("-n", "--native-sysroot", + help="path to the native sysroot containing the tools") + subparser.add_argument("-r", dest="recursive_delete", action="store_true", default=False, + help="remove directories and their contents recursively, " + " this only applies to ext* partition") + +def expandtype(rules): + """ + Custom type for ArgumentParser + Converts expand rules to the dictionary {<partition>: size} + """ + if rules == 'auto': + return {} + result = {} + for rule in rules.split(','): + try: + part, size = rule.split(':') + except ValueError: + raise argparse.ArgumentTypeError("Incorrect rule format: %s" % rule) + + if not part.isdigit(): + raise argparse.ArgumentTypeError("Rule '%s': partition number must be integer" % rule) + + # validate size + multiplier = 1 + for suffix, mult in [('K', 1024), ('M', 1024 * 1024), ('G', 1024 * 1024 * 1024)]: + if size.upper().endswith(suffix): + multiplier = mult + size = size[:-1] + break + if not size.isdigit(): + raise argparse.ArgumentTypeError("Rule '%s': size must be integer" % rule) + + result[int(part)] = int(size) * multiplier + + return result + +def wic_init_parser_write(subparser): + subparser.add_argument("image", + help="path to the wic image") + subparser.add_argument("target", + help="target file or device") + subparser.add_argument("-e", "--expand", type=expandtype, + help="expand rules: auto or <partition>:<size>[,<partition>:<size>]") + subparser.add_argument("-n", "--native-sysroot", + help="path to the native sysroot containing the tools") + +def wic_init_parser_help(subparser): + helpparsers = subparser.add_subparsers(dest='help_topic', help=hlp.wic_usage) + for helptopic in helptopics: + helpparsers.add_parser(helptopic, help=helptopics[helptopic][2]) + return + + +subcommands = { + "create": [wic_create_subcommand, + hlp.wic_create_usage, + hlp.wic_create_help, + wic_init_parser_create], + "list": [wic_list_subcommand, + hlp.wic_list_usage, + hlp.wic_list_help, + wic_init_parser_list], + "ls": [wic_ls_subcommand, + hlp.wic_ls_usage, + hlp.wic_ls_help, + wic_init_parser_ls], + "cp": [wic_cp_subcommand, + hlp.wic_cp_usage, + hlp.wic_cp_help, + wic_init_parser_cp], + "rm": [wic_rm_subcommand, + hlp.wic_rm_usage, + hlp.wic_rm_help, + wic_init_parser_rm], + "write": [wic_write_subcommand, + hlp.wic_write_usage, + hlp.wic_write_help, + wic_init_parser_write], + "help": [wic_help_subcommand, + wic_help_topic_usage, + hlp.wic_help_help, + wic_init_parser_help] } -def start_logging(loglevel): - logging.basicConfig(filename='wic.log', filemode='w', level=loglevel) +def init_parser(parser): + parser.add_argument("--version", action="version", + version="%(prog)s {version}".format(version=__version__)) + parser.add_argument("-D", "--debug", dest="debug", action="store_true", + default=False, help="output debug information") + + subparsers = parser.add_subparsers(dest='command', help=hlp.wic_usage) + for subcmd in subcommands: + subparser = subparsers.add_parser(subcmd, help=subcommands[subcmd][2]) + subcommands[subcmd][3](subparser) +class WicArgumentParser(argparse.ArgumentParser): + def format_help(self): + return hlp.wic_help def main(argv): - parser = optparse.OptionParser(version="wic version %s" % __version__, - usage=hlp.wic_usage) + parser = WicArgumentParser( + description="wic version %s" % __version__) - parser.disable_interspersed_args() + init_parser(parser) - args = parser.parse_args(argv)[1] + args = parser.parse_args(argv) - if len(args): - if args[0] == "help": - if len(args) == 1: + if args.debug: + logger.setLevel(logging.DEBUG) + + if "command" in vars(args): + if args.command == "help": + if args.help_topic is None: parser.print_help() - sys.exit(1) + elif args.help_topic in helptopics: + hlpt = helptopics[args.help_topic] + hlpt[0](hlpt[1], hlpt[2]) + return 0 + + # validate wic cp src and dest parameter to identify which one of it is + # image and cast it into imgtype + if args.command == "cp": + if ":" in args.dest: + args.dest = imgtype(args.dest) + elif ":" in args.src: + args.src = imgtype(args.src) + else: + raise argparse.ArgumentTypeError("no image or partition number specified.") return hlp.invoke_subcommand(args, parser, hlp.wic_help_usage, subcommands) @@ -318,6 +546,6 @@ if __name__ == "__main__": try: sys.exit(main(sys.argv[1:])) except WicError as err: - print("ERROR:", err, file=sys.stderr) + print() + logger.error(err) sys.exit(1) - |