misc.py 8.3 KB

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