123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- #
- # Copyright (c) 2013, Intel Corporation.
- #
- # SPDX-License-Identifier: GPL-2.0-only
- #
- # DESCRIPTION
- # This module provides a place to collect various wic-related utils
- # for the OpenEmbedded Image Tools.
- #
- # AUTHORS
- # Tom Zanussi <tom.zanussi (at] linux.intel.com>
- #
- """Miscellaneous functions."""
- import logging
- import os
- import re
- import subprocess
- from collections import defaultdict
- from distutils import spawn
- from wic import WicError
- logger = logging.getLogger('wic')
- # executable -> recipe pairs for exec_native_cmd
- NATIVE_RECIPES = {"bmaptool": "bmap-tools",
- "grub-mkimage": "grub-efi",
- "isohybrid": "syslinux",
- "mcopy": "mtools",
- "mdel" : "mtools",
- "mdeltree" : "mtools",
- "mdir" : "mtools",
- "mkdosfs": "dosfstools",
- "mkisofs": "cdrtools",
- "mkfs.btrfs": "btrfs-tools",
- "mkfs.ext2": "e2fsprogs",
- "mkfs.ext3": "e2fsprogs",
- "mkfs.ext4": "e2fsprogs",
- "mkfs.vfat": "dosfstools",
- "mksquashfs": "squashfs-tools",
- "mkswap": "util-linux",
- "mmd": "mtools",
- "parted": "parted",
- "sfdisk": "util-linux",
- "sgdisk": "gptfdisk",
- "syslinux": "syslinux",
- "tar": "tar"
- }
- def runtool(cmdln_or_args):
- """ wrapper for most of the subprocess calls
- input:
- cmdln_or_args: can be both args and cmdln str (shell=True)
- return:
- rc, output
- """
- if isinstance(cmdln_or_args, list):
- cmd = cmdln_or_args[0]
- shell = False
- else:
- import shlex
- cmd = shlex.split(cmdln_or_args)[0]
- shell = True
- sout = subprocess.PIPE
- serr = subprocess.STDOUT
- try:
- process = subprocess.Popen(cmdln_or_args, stdout=sout,
- stderr=serr, shell=shell)
- sout, serr = process.communicate()
- # combine stdout and stderr, filter None out and decode
- out = ''.join([out.decode('utf-8') for out in [sout, serr] if out])
- except OSError as err:
- if err.errno == 2:
- # [Errno 2] No such file or directory
- raise WicError('Cannot run command: %s, lost dependency?' % cmd)
- else:
- raise # relay
- return process.returncode, out
- def _exec_cmd(cmd_and_args, as_shell=False):
- """
- Execute command, catching stderr, stdout
- Need to execute as_shell if the command uses wildcards
- """
- logger.debug("_exec_cmd: %s", cmd_and_args)
- args = cmd_and_args.split()
- logger.debug(args)
- if as_shell:
- ret, out = runtool(cmd_and_args)
- else:
- ret, out = runtool(args)
- out = out.strip()
- if ret != 0:
- raise WicError("_exec_cmd: %s returned '%s' instead of 0\noutput: %s" % \
- (cmd_and_args, ret, out))
- logger.debug("_exec_cmd: output for %s (rc = %d): %s",
- cmd_and_args, ret, out)
- return ret, out
- def exec_cmd(cmd_and_args, as_shell=False):
- """
- Execute command, return output
- """
- return _exec_cmd(cmd_and_args, as_shell)[1]
- def find_executable(cmd, paths):
- recipe = cmd
- if recipe in NATIVE_RECIPES:
- recipe = NATIVE_RECIPES[recipe]
- provided = get_bitbake_var("ASSUME_PROVIDED")
- if provided and "%s-native" % recipe in provided:
- return True
- return spawn.find_executable(cmd, paths)
- def exec_native_cmd(cmd_and_args, native_sysroot, pseudo=""):
- """
- Execute native command, catching stderr, stdout
- Need to execute as_shell if the command uses wildcards
- Always need to execute native commands as_shell
- """
- # The reason -1 is used is because there may be "export" commands.
- args = cmd_and_args.split(';')[-1].split()
- logger.debug(args)
- if pseudo:
- cmd_and_args = pseudo + cmd_and_args
- native_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin:%s/bin" % \
- (native_sysroot, native_sysroot,
- native_sysroot, native_sysroot)
- native_cmd_and_args = "export PATH=%s:$PATH;%s" % \
- (native_paths, cmd_and_args)
- logger.debug("exec_native_cmd: %s", native_cmd_and_args)
- # If the command isn't in the native sysroot say we failed.
- if find_executable(args[0], native_paths):
- ret, out = _exec_cmd(native_cmd_and_args, True)
- else:
- ret = 127
- out = "can't find native executable %s in %s" % (args[0], native_paths)
- prog = args[0]
- # shell command-not-found
- if ret == 127 \
- or (pseudo and ret == 1 and out == "Can't find '%s' in $PATH." % prog):
- msg = "A native program %s required to build the image "\
- "was not found (see details above).\n\n" % prog
- recipe = NATIVE_RECIPES.get(prog)
- if recipe:
- msg += "Please make sure wic-tools have %s-native in its DEPENDS, "\
- "build it with 'bitbake wic-tools' and try again.\n" % recipe
- else:
- msg += "Wic failed to find a recipe to build native %s. Please "\
- "file a bug against wic.\n" % prog
- raise WicError(msg)
- return ret, out
- BOOTDD_EXTRA_SPACE = 16384
- class BitbakeVars(defaultdict):
- """
- Container for Bitbake variables.
- """
- def __init__(self):
- defaultdict.__init__(self, dict)
- # default_image and vars_dir attributes should be set from outside
- self.default_image = None
- self.vars_dir = None
- def _parse_line(self, line, image, matcher=re.compile(r"^([a-zA-Z0-9\-_+./~]+)=(.*)")):
- """
- Parse one line from bitbake -e output or from .env file.
- Put result key-value pair into the storage.
- """
- if "=" not in line:
- return
- match = matcher.match(line)
- if not match:
- return
- key, val = match.groups()
- self[image][key] = val.strip('"')
- def get_var(self, var, image=None, cache=True):
- """
- Get bitbake variable from 'bitbake -e' output or from .env file.
- This is a lazy method, i.e. it runs bitbake or parses file only when
- only when variable is requested. It also caches results.
- """
- if not image:
- image = self.default_image
- if image not in self:
- if image and self.vars_dir:
- fname = os.path.join(self.vars_dir, image + '.env')
- if os.path.isfile(fname):
- # parse .env file
- with open(fname) as varsfile:
- for line in varsfile:
- self._parse_line(line, image)
- else:
- print("Couldn't get bitbake variable from %s." % fname)
- print("File %s doesn't exist." % fname)
- return
- else:
- # Get bitbake -e output
- cmd = "bitbake -e"
- if image:
- cmd += " %s" % image
- log_level = logger.getEffectiveLevel()
- logger.setLevel(logging.INFO)
- ret, lines = _exec_cmd(cmd)
- logger.setLevel(log_level)
- if ret:
- logger.error("Couldn't get '%s' output.", cmd)
- logger.error("Bitbake failed with error:\n%s\n", lines)
- return
- # Parse bitbake -e output
- for line in lines.split('\n'):
- self._parse_line(line, image)
- # Make first image a default set of variables
- if cache:
- images = [key for key in self if key]
- if len(images) == 1:
- self[None] = self[image]
- result = self[image].get(var)
- if not cache:
- self.pop(image, None)
- return result
- # Create BB_VARS singleton
- BB_VARS = BitbakeVars()
- def get_bitbake_var(var, image=None, cache=True):
- """
- Provide old get_bitbake_var API by wrapping
- get_var method of BB_VARS singleton.
- """
- return BB_VARS.get_var(var, image, cache)
|