1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# Copyright (c) 2013, Intel Corporation. 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# DESCRIPTION 7*4882a593Smuzhiyun# This module provides a place to collect various wic-related utils 8*4882a593Smuzhiyun# for the OpenEmbedded Image Tools. 9*4882a593Smuzhiyun# 10*4882a593Smuzhiyun# AUTHORS 11*4882a593Smuzhiyun# Tom Zanussi <tom.zanussi (at] linux.intel.com> 12*4882a593Smuzhiyun# 13*4882a593Smuzhiyun"""Miscellaneous functions.""" 14*4882a593Smuzhiyun 15*4882a593Smuzhiyunimport logging 16*4882a593Smuzhiyunimport os 17*4882a593Smuzhiyunimport re 18*4882a593Smuzhiyunimport subprocess 19*4882a593Smuzhiyunimport shutil 20*4882a593Smuzhiyun 21*4882a593Smuzhiyunfrom collections import defaultdict 22*4882a593Smuzhiyun 23*4882a593Smuzhiyunfrom wic import WicError 24*4882a593Smuzhiyun 25*4882a593Smuzhiyunlogger = logging.getLogger('wic') 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun# executable -> recipe pairs for exec_native_cmd 28*4882a593SmuzhiyunNATIVE_RECIPES = {"bmaptool": "bmap-tools", 29*4882a593Smuzhiyun "dumpe2fs": "e2fsprogs", 30*4882a593Smuzhiyun "grub-mkimage": "grub-efi", 31*4882a593Smuzhiyun "isohybrid": "syslinux", 32*4882a593Smuzhiyun "mcopy": "mtools", 33*4882a593Smuzhiyun "mdel" : "mtools", 34*4882a593Smuzhiyun "mdeltree" : "mtools", 35*4882a593Smuzhiyun "mdir" : "mtools", 36*4882a593Smuzhiyun "mkdosfs": "dosfstools", 37*4882a593Smuzhiyun "mkisofs": "cdrtools", 38*4882a593Smuzhiyun "mkfs.btrfs": "btrfs-tools", 39*4882a593Smuzhiyun "mkfs.ext2": "e2fsprogs", 40*4882a593Smuzhiyun "mkfs.ext3": "e2fsprogs", 41*4882a593Smuzhiyun "mkfs.ext4": "e2fsprogs", 42*4882a593Smuzhiyun "mkfs.vfat": "dosfstools", 43*4882a593Smuzhiyun "mksquashfs": "squashfs-tools", 44*4882a593Smuzhiyun "mkswap": "util-linux", 45*4882a593Smuzhiyun "mmd": "mtools", 46*4882a593Smuzhiyun "parted": "parted", 47*4882a593Smuzhiyun "sfdisk": "util-linux", 48*4882a593Smuzhiyun "sgdisk": "gptfdisk", 49*4882a593Smuzhiyun "syslinux": "syslinux", 50*4882a593Smuzhiyun "tar": "tar" 51*4882a593Smuzhiyun } 52*4882a593Smuzhiyun 53*4882a593Smuzhiyundef runtool(cmdln_or_args): 54*4882a593Smuzhiyun """ wrapper for most of the subprocess calls 55*4882a593Smuzhiyun input: 56*4882a593Smuzhiyun cmdln_or_args: can be both args and cmdln str (shell=True) 57*4882a593Smuzhiyun return: 58*4882a593Smuzhiyun rc, output 59*4882a593Smuzhiyun """ 60*4882a593Smuzhiyun if isinstance(cmdln_or_args, list): 61*4882a593Smuzhiyun cmd = cmdln_or_args[0] 62*4882a593Smuzhiyun shell = False 63*4882a593Smuzhiyun else: 64*4882a593Smuzhiyun import shlex 65*4882a593Smuzhiyun cmd = shlex.split(cmdln_or_args)[0] 66*4882a593Smuzhiyun shell = True 67*4882a593Smuzhiyun 68*4882a593Smuzhiyun sout = subprocess.PIPE 69*4882a593Smuzhiyun serr = subprocess.STDOUT 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun try: 72*4882a593Smuzhiyun process = subprocess.Popen(cmdln_or_args, stdout=sout, 73*4882a593Smuzhiyun stderr=serr, shell=shell) 74*4882a593Smuzhiyun sout, serr = process.communicate() 75*4882a593Smuzhiyun # combine stdout and stderr, filter None out and decode 76*4882a593Smuzhiyun out = ''.join([out.decode('utf-8') for out in [sout, serr] if out]) 77*4882a593Smuzhiyun except OSError as err: 78*4882a593Smuzhiyun if err.errno == 2: 79*4882a593Smuzhiyun # [Errno 2] No such file or directory 80*4882a593Smuzhiyun raise WicError('Cannot run command: %s, lost dependency?' % cmd) 81*4882a593Smuzhiyun else: 82*4882a593Smuzhiyun raise # relay 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun return process.returncode, out 85*4882a593Smuzhiyun 86*4882a593Smuzhiyundef _exec_cmd(cmd_and_args, as_shell=False): 87*4882a593Smuzhiyun """ 88*4882a593Smuzhiyun Execute command, catching stderr, stdout 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun Need to execute as_shell if the command uses wildcards 91*4882a593Smuzhiyun """ 92*4882a593Smuzhiyun logger.debug("_exec_cmd: %s", cmd_and_args) 93*4882a593Smuzhiyun args = cmd_and_args.split() 94*4882a593Smuzhiyun logger.debug(args) 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun if as_shell: 97*4882a593Smuzhiyun ret, out = runtool(cmd_and_args) 98*4882a593Smuzhiyun else: 99*4882a593Smuzhiyun ret, out = runtool(args) 100*4882a593Smuzhiyun out = out.strip() 101*4882a593Smuzhiyun if ret != 0: 102*4882a593Smuzhiyun raise WicError("_exec_cmd: %s returned '%s' instead of 0\noutput: %s" % \ 103*4882a593Smuzhiyun (cmd_and_args, ret, out)) 104*4882a593Smuzhiyun 105*4882a593Smuzhiyun logger.debug("_exec_cmd: output for %s (rc = %d): %s", 106*4882a593Smuzhiyun cmd_and_args, ret, out) 107*4882a593Smuzhiyun 108*4882a593Smuzhiyun return ret, out 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun 111*4882a593Smuzhiyundef exec_cmd(cmd_and_args, as_shell=False): 112*4882a593Smuzhiyun """ 113*4882a593Smuzhiyun Execute command, return output 114*4882a593Smuzhiyun """ 115*4882a593Smuzhiyun return _exec_cmd(cmd_and_args, as_shell)[1] 116*4882a593Smuzhiyun 117*4882a593Smuzhiyundef find_executable(cmd, paths): 118*4882a593Smuzhiyun recipe = cmd 119*4882a593Smuzhiyun if recipe in NATIVE_RECIPES: 120*4882a593Smuzhiyun recipe = NATIVE_RECIPES[recipe] 121*4882a593Smuzhiyun provided = get_bitbake_var("ASSUME_PROVIDED") 122*4882a593Smuzhiyun if provided and "%s-native" % recipe in provided: 123*4882a593Smuzhiyun return True 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun return shutil.which(cmd, path=paths) 126*4882a593Smuzhiyun 127*4882a593Smuzhiyundef exec_native_cmd(cmd_and_args, native_sysroot, pseudo=""): 128*4882a593Smuzhiyun """ 129*4882a593Smuzhiyun Execute native command, catching stderr, stdout 130*4882a593Smuzhiyun 131*4882a593Smuzhiyun Need to execute as_shell if the command uses wildcards 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun Always need to execute native commands as_shell 134*4882a593Smuzhiyun """ 135*4882a593Smuzhiyun # The reason -1 is used is because there may be "export" commands. 136*4882a593Smuzhiyun args = cmd_and_args.split(';')[-1].split() 137*4882a593Smuzhiyun logger.debug(args) 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun if pseudo: 140*4882a593Smuzhiyun cmd_and_args = pseudo + cmd_and_args 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun hosttools_dir = get_bitbake_var("HOSTTOOLS_DIR") 143*4882a593Smuzhiyun target_sys = get_bitbake_var("TARGET_SYS") 144*4882a593Smuzhiyun 145*4882a593Smuzhiyun native_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin:%s/usr/bin/%s:%s/bin:%s" % \ 146*4882a593Smuzhiyun (native_sysroot, native_sysroot, 147*4882a593Smuzhiyun native_sysroot, native_sysroot, target_sys, 148*4882a593Smuzhiyun native_sysroot, hosttools_dir) 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun native_cmd_and_args = "export PATH=%s:$PATH;%s" % \ 151*4882a593Smuzhiyun (native_paths, cmd_and_args) 152*4882a593Smuzhiyun logger.debug("exec_native_cmd: %s", native_cmd_and_args) 153*4882a593Smuzhiyun 154*4882a593Smuzhiyun # If the command isn't in the native sysroot say we failed. 155*4882a593Smuzhiyun if find_executable(args[0], native_paths): 156*4882a593Smuzhiyun ret, out = _exec_cmd(native_cmd_and_args, True) 157*4882a593Smuzhiyun else: 158*4882a593Smuzhiyun ret = 127 159*4882a593Smuzhiyun out = "can't find native executable %s in %s" % (args[0], native_paths) 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun prog = args[0] 162*4882a593Smuzhiyun # shell command-not-found 163*4882a593Smuzhiyun if ret == 127 \ 164*4882a593Smuzhiyun or (pseudo and ret == 1 and out == "Can't find '%s' in $PATH." % prog): 165*4882a593Smuzhiyun msg = "A native program %s required to build the image "\ 166*4882a593Smuzhiyun "was not found (see details above).\n\n" % prog 167*4882a593Smuzhiyun recipe = NATIVE_RECIPES.get(prog) 168*4882a593Smuzhiyun if recipe: 169*4882a593Smuzhiyun msg += "Please make sure wic-tools have %s-native in its DEPENDS, "\ 170*4882a593Smuzhiyun "build it with 'bitbake wic-tools' and try again.\n" % recipe 171*4882a593Smuzhiyun else: 172*4882a593Smuzhiyun msg += "Wic failed to find a recipe to build native %s. Please "\ 173*4882a593Smuzhiyun "file a bug against wic.\n" % prog 174*4882a593Smuzhiyun raise WicError(msg) 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun return ret, out 177*4882a593Smuzhiyun 178*4882a593SmuzhiyunBOOTDD_EXTRA_SPACE = 16384 179*4882a593Smuzhiyun 180*4882a593Smuzhiyunclass BitbakeVars(defaultdict): 181*4882a593Smuzhiyun """ 182*4882a593Smuzhiyun Container for Bitbake variables. 183*4882a593Smuzhiyun """ 184*4882a593Smuzhiyun def __init__(self): 185*4882a593Smuzhiyun defaultdict.__init__(self, dict) 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun # default_image and vars_dir attributes should be set from outside 188*4882a593Smuzhiyun self.default_image = None 189*4882a593Smuzhiyun self.vars_dir = None 190*4882a593Smuzhiyun 191*4882a593Smuzhiyun def _parse_line(self, line, image, matcher=re.compile(r"^([a-zA-Z0-9\-_+./~]+)=(.*)")): 192*4882a593Smuzhiyun """ 193*4882a593Smuzhiyun Parse one line from bitbake -e output or from .env file. 194*4882a593Smuzhiyun Put result key-value pair into the storage. 195*4882a593Smuzhiyun """ 196*4882a593Smuzhiyun if "=" not in line: 197*4882a593Smuzhiyun return 198*4882a593Smuzhiyun match = matcher.match(line) 199*4882a593Smuzhiyun if not match: 200*4882a593Smuzhiyun return 201*4882a593Smuzhiyun key, val = match.groups() 202*4882a593Smuzhiyun self[image][key] = val.strip('"') 203*4882a593Smuzhiyun 204*4882a593Smuzhiyun def get_var(self, var, image=None, cache=True): 205*4882a593Smuzhiyun """ 206*4882a593Smuzhiyun Get bitbake variable from 'bitbake -e' output or from .env file. 207*4882a593Smuzhiyun This is a lazy method, i.e. it runs bitbake or parses file only when 208*4882a593Smuzhiyun only when variable is requested. It also caches results. 209*4882a593Smuzhiyun """ 210*4882a593Smuzhiyun if not image: 211*4882a593Smuzhiyun image = self.default_image 212*4882a593Smuzhiyun 213*4882a593Smuzhiyun if image not in self: 214*4882a593Smuzhiyun if image and self.vars_dir: 215*4882a593Smuzhiyun fname = os.path.join(self.vars_dir, image + '.env') 216*4882a593Smuzhiyun if os.path.isfile(fname): 217*4882a593Smuzhiyun # parse .env file 218*4882a593Smuzhiyun with open(fname) as varsfile: 219*4882a593Smuzhiyun for line in varsfile: 220*4882a593Smuzhiyun self._parse_line(line, image) 221*4882a593Smuzhiyun else: 222*4882a593Smuzhiyun print("Couldn't get bitbake variable from %s." % fname) 223*4882a593Smuzhiyun print("File %s doesn't exist." % fname) 224*4882a593Smuzhiyun return 225*4882a593Smuzhiyun else: 226*4882a593Smuzhiyun # Get bitbake -e output 227*4882a593Smuzhiyun cmd = "bitbake -e" 228*4882a593Smuzhiyun if image: 229*4882a593Smuzhiyun cmd += " %s" % image 230*4882a593Smuzhiyun 231*4882a593Smuzhiyun log_level = logger.getEffectiveLevel() 232*4882a593Smuzhiyun logger.setLevel(logging.INFO) 233*4882a593Smuzhiyun ret, lines = _exec_cmd(cmd) 234*4882a593Smuzhiyun logger.setLevel(log_level) 235*4882a593Smuzhiyun 236*4882a593Smuzhiyun if ret: 237*4882a593Smuzhiyun logger.error("Couldn't get '%s' output.", cmd) 238*4882a593Smuzhiyun logger.error("Bitbake failed with error:\n%s\n", lines) 239*4882a593Smuzhiyun return 240*4882a593Smuzhiyun 241*4882a593Smuzhiyun # Parse bitbake -e output 242*4882a593Smuzhiyun for line in lines.split('\n'): 243*4882a593Smuzhiyun self._parse_line(line, image) 244*4882a593Smuzhiyun 245*4882a593Smuzhiyun # Make first image a default set of variables 246*4882a593Smuzhiyun if cache: 247*4882a593Smuzhiyun images = [key for key in self if key] 248*4882a593Smuzhiyun if len(images) == 1: 249*4882a593Smuzhiyun self[None] = self[image] 250*4882a593Smuzhiyun 251*4882a593Smuzhiyun result = self[image].get(var) 252*4882a593Smuzhiyun if not cache: 253*4882a593Smuzhiyun self.pop(image, None) 254*4882a593Smuzhiyun 255*4882a593Smuzhiyun return result 256*4882a593Smuzhiyun 257*4882a593Smuzhiyun# Create BB_VARS singleton 258*4882a593SmuzhiyunBB_VARS = BitbakeVars() 259*4882a593Smuzhiyun 260*4882a593Smuzhiyundef get_bitbake_var(var, image=None, cache=True): 261*4882a593Smuzhiyun """ 262*4882a593Smuzhiyun Provide old get_bitbake_var API by wrapping 263*4882a593Smuzhiyun get_var method of BB_VARS singleton. 264*4882a593Smuzhiyun """ 265*4882a593Smuzhiyun return BB_VARS.get_var(var, image, cache) 266