xref: /openbmc/openbmc/poky/scripts/wic (revision d159c7fb)
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