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