scriptutils.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. # Script utility functions
  2. #
  3. # Copyright (C) 2014 Intel Corporation
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License version 2 as
  7. # published by the Free Software Foundation.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License along
  15. # with this program; if not, write to the Free Software Foundation, Inc.,
  16. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. import argparse
  18. import glob
  19. import logging
  20. import os
  21. import random
  22. import shlex
  23. import shutil
  24. import string
  25. import subprocess
  26. import sys
  27. import tempfile
  28. import importlib
  29. from importlib import machinery
  30. def logger_create(name, stream=None):
  31. logger = logging.getLogger(name)
  32. loggerhandler = logging.StreamHandler(stream=stream)
  33. loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
  34. logger.addHandler(loggerhandler)
  35. logger.setLevel(logging.INFO)
  36. return logger
  37. def logger_setup_color(logger, color='auto'):
  38. from bb.msg import BBLogFormatter
  39. console = logging.StreamHandler(sys.stdout)
  40. formatter = BBLogFormatter("%(levelname)s: %(message)s")
  41. console.setFormatter(formatter)
  42. logger.handlers = [console]
  43. if color == 'always' or (color=='auto' and console.stream.isatty()):
  44. formatter.enable_color()
  45. def load_plugins(logger, plugins, pluginpath):
  46. import imp
  47. def load_plugin(name):
  48. logger.debug('Loading plugin %s' % name)
  49. spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
  50. if spec:
  51. return spec.loader.load_module()
  52. def plugin_name(filename):
  53. return os.path.splitext(os.path.basename(filename))[0]
  54. known_plugins = [plugin_name(p.__name__) for p in plugins]
  55. logger.debug('Loading plugins from %s...' % pluginpath)
  56. for fn in glob.glob(os.path.join(pluginpath, '*.py')):
  57. name = plugin_name(fn)
  58. if name != '__init__' and name not in known_plugins:
  59. plugin = load_plugin(name)
  60. if hasattr(plugin, 'plugin_init'):
  61. plugin.plugin_init(plugins)
  62. plugins.append(plugin)
  63. def git_convert_standalone_clone(repodir):
  64. """If specified directory is a git repository, ensure it's a standalone clone"""
  65. import bb.process
  66. if os.path.exists(os.path.join(repodir, '.git')):
  67. alternatesfile = os.path.join(repodir, '.git', 'objects', 'info', 'alternates')
  68. if os.path.exists(alternatesfile):
  69. # This will have been cloned with -s, so we need to convert it so none
  70. # of the contents is shared
  71. bb.process.run('git repack -a', cwd=repodir)
  72. os.remove(alternatesfile)
  73. def _get_temp_recipe_dir(d):
  74. # This is a little bit hacky but we need to find a place where we can put
  75. # the recipe so that bitbake can find it. We're going to delete it at the
  76. # end so it doesn't really matter where we put it.
  77. bbfiles = d.getVar('BBFILES').split()
  78. fetchrecipedir = None
  79. for pth in bbfiles:
  80. if pth.endswith('.bb'):
  81. pthdir = os.path.dirname(pth)
  82. if os.access(os.path.dirname(os.path.dirname(pthdir)), os.W_OK):
  83. fetchrecipedir = pthdir.replace('*', 'recipetool')
  84. if pthdir.endswith('workspace/recipes/*'):
  85. # Prefer the workspace
  86. break
  87. return fetchrecipedir
  88. class FetchUrlFailure(Exception):
  89. def __init__(self, url):
  90. self.url = url
  91. def __str__(self):
  92. return "Failed to fetch URL %s" % self.url
  93. def fetch_url(tinfoil, srcuri, srcrev, destdir, logger, preserve_tmp=False, mirrors=False):
  94. """
  95. Fetch the specified URL using normal do_fetch and do_unpack tasks, i.e.
  96. any dependencies that need to be satisfied in order to support the fetch
  97. operation will be taken care of
  98. """
  99. import bb
  100. checksums = {}
  101. fetchrecipepn = None
  102. # We need to put our temp directory under ${BASE_WORKDIR} otherwise
  103. # we may have problems with the recipe-specific sysroot population
  104. tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
  105. bb.utils.mkdirhier(tmpparent)
  106. tmpdir = tempfile.mkdtemp(prefix='recipetool-', dir=tmpparent)
  107. try:
  108. tmpworkdir = os.path.join(tmpdir, 'work')
  109. logger.debug('fetch_url: temp dir is %s' % tmpdir)
  110. fetchrecipedir = _get_temp_recipe_dir(tinfoil.config_data)
  111. if not fetchrecipedir:
  112. logger.error('Searched BBFILES but unable to find a writeable place to put temporary recipe')
  113. sys.exit(1)
  114. fetchrecipe = None
  115. bb.utils.mkdirhier(fetchrecipedir)
  116. try:
  117. # Generate a dummy recipe so we can follow more or less normal paths
  118. # for do_fetch and do_unpack
  119. # I'd use tempfile functions here but underscores can be produced by that and those
  120. # aren't allowed in recipe file names except to separate the version
  121. rndstring = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(8))
  122. fetchrecipe = os.path.join(fetchrecipedir, 'tmp-recipetool-%s.bb' % rndstring)
  123. fetchrecipepn = os.path.splitext(os.path.basename(fetchrecipe))[0]
  124. logger.debug('Generating initial recipe %s for fetching' % fetchrecipe)
  125. with open(fetchrecipe, 'w') as f:
  126. # We don't want to have to specify LIC_FILES_CHKSUM
  127. f.write('LICENSE = "CLOSED"\n')
  128. # We don't need the cross-compiler
  129. f.write('INHIBIT_DEFAULT_DEPS = "1"\n')
  130. # We don't have the checksums yet so we can't require them
  131. f.write('BB_STRICT_CHECKSUM = "ignore"\n')
  132. f.write('SRC_URI = "%s"\n' % srcuri)
  133. f.write('SRCREV = "%s"\n' % srcrev)
  134. f.write('WORKDIR = "%s"\n' % tmpworkdir)
  135. # Set S out of the way so it doesn't get created under the workdir
  136. f.write('S = "%s"\n' % os.path.join(tmpdir, 'emptysrc'))
  137. if not mirrors:
  138. # We do not need PREMIRRORS since we are almost certainly
  139. # fetching new source rather than something that has already
  140. # been fetched. Hence, we disable them by default.
  141. # However, we provide an option for users to enable it.
  142. f.write('PREMIRRORS = ""\n')
  143. f.write('MIRRORS = ""\n')
  144. logger.info('Fetching %s...' % srcuri)
  145. # FIXME this is too noisy at the moment
  146. # Parse recipes so our new recipe gets picked up
  147. tinfoil.parse_recipes()
  148. def eventhandler(event):
  149. if isinstance(event, bb.fetch2.MissingChecksumEvent):
  150. checksums.update(event.checksums)
  151. return True
  152. return False
  153. # Run the fetch + unpack tasks
  154. res = tinfoil.build_targets(fetchrecipepn,
  155. 'do_unpack',
  156. handle_events=True,
  157. extra_events=['bb.fetch2.MissingChecksumEvent'],
  158. event_callback=eventhandler)
  159. if not res:
  160. raise FetchUrlFailure(srcuri)
  161. # Remove unneeded directories
  162. rd = tinfoil.parse_recipe(fetchrecipepn)
  163. if rd:
  164. pathvars = ['T', 'RECIPE_SYSROOT', 'RECIPE_SYSROOT_NATIVE']
  165. for pathvar in pathvars:
  166. path = rd.getVar(pathvar)
  167. shutil.rmtree(path)
  168. finally:
  169. if fetchrecipe:
  170. try:
  171. os.remove(fetchrecipe)
  172. except FileNotFoundError:
  173. pass
  174. try:
  175. os.rmdir(fetchrecipedir)
  176. except OSError as e:
  177. import errno
  178. if e.errno != errno.ENOTEMPTY:
  179. raise
  180. bb.utils.mkdirhier(destdir)
  181. for fn in os.listdir(tmpworkdir):
  182. shutil.move(os.path.join(tmpworkdir, fn), destdir)
  183. finally:
  184. if not preserve_tmp:
  185. shutil.rmtree(tmpdir)
  186. tmpdir = None
  187. return checksums, tmpdir
  188. def run_editor(fn, logger=None):
  189. if isinstance(fn, str):
  190. files = [fn]
  191. else:
  192. files = fn
  193. editor = os.getenv('VISUAL', os.getenv('EDITOR', 'vi'))
  194. try:
  195. #print(shlex.split(editor) + files)
  196. return subprocess.check_call(shlex.split(editor) + files)
  197. except subprocess.CalledProcessError as exc:
  198. logger.error("Execution of '%s' failed: %s" % (editor, exc))
  199. return 1
  200. def is_src_url(param):
  201. """
  202. Check if a parameter is a URL and return True if so
  203. NOTE: be careful about changing this as it will influence how devtool/recipetool command line handling works
  204. """
  205. if not param:
  206. return False
  207. elif '://' in param:
  208. return True
  209. elif param.startswith('git@') or ('@' in param and param.endswith('.git')):
  210. return True
  211. return False