blob: a741aed364bff816fb3e97a397c772f7f9832900 [file] [log] [blame]
lh9ed821d2023-04-07 01:36:19 -07001#!/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)