xref: /openbmc/openbmc/poky/scripts/wic (revision 595f6308)
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
25import shutil
26
27from collections import namedtuple
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 = shutil.which('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    subparser.add_argument("--extra-space", type=int, dest="extra_space",
350                      default=0, help="additional free disk space to add to the image")
351    return
352
353
354def wic_init_parser_list(subparser):
355    subparser.add_argument("list_type",
356                        help="can be 'images' or 'source-plugins' "
357                             "to obtain a list. "
358                             "If value is a valid .wks image file")
359    subparser.add_argument("help_for", default=[], nargs='*',
360                        help="If 'list_type' is a valid .wks image file "
361                             "this value can be 'help' to show the help information "
362                             "defined inside the .wks file")
363    return
364
365def imgtype(arg):
366    """
367    Custom type for ArgumentParser
368    Converts path spec to named tuple: (image, partition, path)
369    """
370    image = arg
371    part = path = None
372    if ':' in image:
373        image, part = image.split(':')
374        if '/' in part:
375            part, path = part.split('/', 1)
376        if not path:
377            path = '/'
378
379    if not os.path.isfile(image):
380        err = "%s is not a regular file or symlink" % image
381        raise argparse.ArgumentTypeError(err)
382
383    return namedtuple('ImgType', 'image part path')(image, part, path)
384
385def wic_init_parser_ls(subparser):
386    subparser.add_argument("path", type=imgtype,
387                        help="image spec: <image>[:<vfat partition>[<path>]]")
388    subparser.add_argument("-n", "--native-sysroot",
389                        help="path to the native sysroot containing the tools")
390
391def imgpathtype(arg):
392    img = imgtype(arg)
393    if img.part is None:
394        raise argparse.ArgumentTypeError("partition number is not specified")
395    return img
396
397def wic_init_parser_cp(subparser):
398    subparser.add_argument("src",
399                        help="image spec: <image>:<vfat partition>[<path>] or <file>")
400    subparser.add_argument("dest",
401                        help="image spec: <image>:<vfat partition>[<path>] or <file>")
402    subparser.add_argument("-n", "--native-sysroot",
403                        help="path to the native sysroot containing the tools")
404
405def wic_init_parser_rm(subparser):
406    subparser.add_argument("path", type=imgpathtype,
407                        help="path: <image>:<vfat partition><path>")
408    subparser.add_argument("-n", "--native-sysroot",
409                        help="path to the native sysroot containing the tools")
410    subparser.add_argument("-r", dest="recursive_delete", action="store_true", default=False,
411                        help="remove directories and their contents recursively, "
412                        " this only applies to ext* partition")
413
414def expandtype(rules):
415    """
416    Custom type for ArgumentParser
417    Converts expand rules to the dictionary {<partition>: size}
418    """
419    if rules == 'auto':
420        return {}
421    result = {}
422    for rule in rules.split(','):
423        try:
424            part, size = rule.split(':')
425        except ValueError:
426            raise argparse.ArgumentTypeError("Incorrect rule format: %s" % rule)
427
428        if not part.isdigit():
429            raise argparse.ArgumentTypeError("Rule '%s': partition number must be integer" % rule)
430
431        # validate size
432        multiplier = 1
433        for suffix, mult in [('K', 1024), ('M', 1024 * 1024), ('G', 1024 * 1024 * 1024)]:
434            if size.upper().endswith(suffix):
435                multiplier = mult
436                size = size[:-1]
437                break
438        if not size.isdigit():
439            raise argparse.ArgumentTypeError("Rule '%s': size must be integer" % rule)
440
441        result[int(part)] = int(size) * multiplier
442
443    return result
444
445def wic_init_parser_write(subparser):
446    subparser.add_argument("image",
447                        help="path to the wic image")
448    subparser.add_argument("target",
449                        help="target file or device")
450    subparser.add_argument("-e", "--expand", type=expandtype,
451                        help="expand rules: auto or <partition>:<size>[,<partition>:<size>]")
452    subparser.add_argument("-n", "--native-sysroot",
453                        help="path to the native sysroot containing the tools")
454
455def wic_init_parser_help(subparser):
456    helpparsers = subparser.add_subparsers(dest='help_topic', help=hlp.wic_usage)
457    for helptopic in helptopics:
458        helpparsers.add_parser(helptopic, help=helptopics[helptopic][2])
459    return
460
461
462subcommands = {
463    "create":    [wic_create_subcommand,
464                  hlp.wic_create_usage,
465                  hlp.wic_create_help,
466                  wic_init_parser_create],
467    "list":      [wic_list_subcommand,
468                  hlp.wic_list_usage,
469                  hlp.wic_list_help,
470                  wic_init_parser_list],
471    "ls":        [wic_ls_subcommand,
472                  hlp.wic_ls_usage,
473                  hlp.wic_ls_help,
474                  wic_init_parser_ls],
475    "cp":        [wic_cp_subcommand,
476                  hlp.wic_cp_usage,
477                  hlp.wic_cp_help,
478                  wic_init_parser_cp],
479    "rm":        [wic_rm_subcommand,
480                  hlp.wic_rm_usage,
481                  hlp.wic_rm_help,
482                  wic_init_parser_rm],
483    "write":     [wic_write_subcommand,
484                  hlp.wic_write_usage,
485                  hlp.wic_write_help,
486                  wic_init_parser_write],
487    "help":      [wic_help_subcommand,
488                  wic_help_topic_usage,
489                  hlp.wic_help_help,
490                  wic_init_parser_help]
491}
492
493
494def init_parser(parser):
495    parser.add_argument("--version", action="version",
496        version="%(prog)s {version}".format(version=__version__))
497    parser.add_argument("-D", "--debug", dest="debug", action="store_true",
498        default=False, help="output debug information")
499
500    subparsers = parser.add_subparsers(dest='command', help=hlp.wic_usage)
501    for subcmd in subcommands:
502        subparser = subparsers.add_parser(subcmd, help=subcommands[subcmd][2])
503        subcommands[subcmd][3](subparser)
504
505class WicArgumentParser(argparse.ArgumentParser):
506     def format_help(self):
507         return hlp.wic_help
508
509def main(argv):
510    parser = WicArgumentParser(
511        description="wic version %s" % __version__)
512
513    init_parser(parser)
514
515    args = parser.parse_args(argv)
516
517    if args.debug:
518        logger.setLevel(logging.DEBUG)
519
520    if "command" in vars(args):
521        if args.command == "help":
522            if args.help_topic is None:
523                parser.print_help()
524            elif args.help_topic in helptopics:
525                hlpt = helptopics[args.help_topic]
526                hlpt[0](hlpt[1], hlpt[2])
527            return 0
528
529    # validate wic cp src and dest parameter to identify which one of it is
530    # image and cast it into imgtype
531    if args.command == "cp":
532        if ":" in args.dest:
533            args.dest = imgtype(args.dest)
534        elif ":" in args.src:
535            args.src = imgtype(args.src)
536        else:
537            raise argparse.ArgumentTypeError("no image or partition number specified.")
538
539    return hlp.invoke_subcommand(args, parser, hlp.wic_help_usage, subcommands)
540
541
542if __name__ == "__main__":
543    try:
544        sys.exit(main(sys.argv[1:]))
545    except WicError as err:
546        print()
547        logger.error(err)
548        sys.exit(1)
549