xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/wic/misc.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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