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