diff options
Diffstat (limited to 'scripts/lib/devtool')
| -rw-r--r-- | scripts/lib/devtool/__init__.py | 78 | ||||
| -rw-r--r-- | scripts/lib/devtool/standard.py | 545 |
2 files changed, 623 insertions, 0 deletions
diff --git a/scripts/lib/devtool/__init__.py b/scripts/lib/devtool/__init__.py new file mode 100644 index 0000000000..3f8158e24a --- /dev/null +++ b/scripts/lib/devtool/__init__.py | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | #!/usr/bin/env python | ||
| 2 | |||
| 3 | # Development tool - utility functions for plugins | ||
| 4 | # | ||
| 5 | # Copyright (C) 2014 Intel Corporation | ||
| 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 | |||
| 21 | import os | ||
| 22 | import sys | ||
| 23 | import subprocess | ||
| 24 | import logging | ||
| 25 | |||
| 26 | logger = logging.getLogger('devtool') | ||
| 27 | |||
| 28 | def exec_build_env_command(init_path, builddir, cmd, watch=False, **options): | ||
| 29 | import bb | ||
| 30 | if not 'cwd' in options: | ||
| 31 | options["cwd"] = builddir | ||
| 32 | if init_path: | ||
| 33 | logger.debug('Executing command: "%s" using init path %s' % (cmd, init_path)) | ||
| 34 | init_prefix = '. %s %s > /dev/null && ' % (init_path, builddir) | ||
| 35 | else: | ||
| 36 | logger.debug('Executing command "%s"' % cmd) | ||
| 37 | init_prefix = '' | ||
| 38 | if watch: | ||
| 39 | if sys.stdout.isatty(): | ||
| 40 | # Fool bitbake into thinking it's outputting to a terminal (because it is, indirectly) | ||
| 41 | cmd = 'script -q -c "%s" /dev/null' % cmd | ||
| 42 | return exec_watch('%s%s' % (init_prefix, cmd), **options) | ||
| 43 | else: | ||
| 44 | return bb.process.run('%s%s' % (init_prefix, cmd), **options) | ||
| 45 | |||
| 46 | def exec_watch(cmd, **options): | ||
| 47 | if isinstance(cmd, basestring) and not "shell" in options: | ||
| 48 | options["shell"] = True | ||
| 49 | |||
| 50 | process = subprocess.Popen( | ||
| 51 | cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **options | ||
| 52 | ) | ||
| 53 | |||
| 54 | buf = '' | ||
| 55 | while True: | ||
| 56 | out = process.stdout.read(1) | ||
| 57 | if out: | ||
| 58 | sys.stdout.write(out) | ||
| 59 | sys.stdout.flush() | ||
| 60 | buf += out | ||
| 61 | elif out == '' and process.poll() != None: | ||
| 62 | break | ||
| 63 | return buf | ||
| 64 | |||
| 65 | def setup_tinfoil(): | ||
| 66 | import scriptpath | ||
| 67 | bitbakepath = scriptpath.add_bitbake_lib_path() | ||
| 68 | if not bitbakepath: | ||
| 69 | logger.error("Unable to find bitbake by searching parent directory of this script or PATH") | ||
| 70 | sys.exit(1) | ||
| 71 | |||
| 72 | import bb.tinfoil | ||
| 73 | import logging | ||
| 74 | tinfoil = bb.tinfoil.Tinfoil() | ||
| 75 | tinfoil.prepare(False) | ||
| 76 | tinfoil.logger.setLevel(logging.WARNING) | ||
| 77 | return tinfoil | ||
| 78 | |||
diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py new file mode 100644 index 0000000000..69bb228487 --- /dev/null +++ b/scripts/lib/devtool/standard.py | |||
| @@ -0,0 +1,545 @@ | |||
| 1 | # Development tool - standard commands plugin | ||
| 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 | |||
| 18 | import os | ||
| 19 | import sys | ||
| 20 | import re | ||
| 21 | import shutil | ||
| 22 | import glob | ||
| 23 | import tempfile | ||
| 24 | import logging | ||
| 25 | import argparse | ||
| 26 | from devtool import exec_build_env_command, setup_tinfoil | ||
| 27 | |||
| 28 | logger = logging.getLogger('devtool') | ||
| 29 | |||
| 30 | def plugin_init(pluginlist): | ||
| 31 | pass | ||
| 32 | |||
| 33 | |||
| 34 | def add(args, config, basepath, workspace): | ||
| 35 | import bb | ||
| 36 | import oe.recipeutils | ||
| 37 | |||
| 38 | if args.recipename in workspace: | ||
| 39 | logger.error("recipe %s is already in your workspace" % args.recipename) | ||
| 40 | return -1 | ||
| 41 | |||
| 42 | reason = oe.recipeutils.validate_pn(args.recipename) | ||
| 43 | if reason: | ||
| 44 | logger.error(reason) | ||
| 45 | return -1 | ||
| 46 | |||
| 47 | srctree = os.path.abspath(args.srctree) | ||
| 48 | appendpath = os.path.join(config.workspace_path, 'appends') | ||
| 49 | if not os.path.exists(appendpath): | ||
| 50 | os.makedirs(appendpath) | ||
| 51 | |||
| 52 | recipedir = os.path.join(config.workspace_path, 'recipes', args.recipename) | ||
| 53 | bb.utils.mkdirhier(recipedir) | ||
| 54 | if args.version: | ||
| 55 | if '_' in args.version or ' ' in args.version: | ||
| 56 | logger.error('Invalid version string "%s"' % args.version) | ||
| 57 | return -1 | ||
| 58 | bp = "%s_%s" % (args.recipename, args.version) | ||
| 59 | else: | ||
| 60 | bp = args.recipename | ||
| 61 | recipefile = os.path.join(recipedir, "%s.bb" % bp) | ||
| 62 | if sys.stdout.isatty(): | ||
| 63 | color = 'always' | ||
| 64 | else: | ||
| 65 | color = args.color | ||
| 66 | stdout, stderr = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create -o %s %s' % (color, recipefile, srctree)) | ||
| 67 | logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile) | ||
| 68 | |||
| 69 | _add_md5(config, args.recipename, recipefile) | ||
| 70 | |||
| 71 | initial_rev = None | ||
| 72 | if os.path.exists(os.path.join(srctree, '.git')): | ||
| 73 | (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree) | ||
| 74 | initial_rev = stdout.rstrip() | ||
| 75 | |||
| 76 | appendfile = os.path.join(appendpath, '%s.bbappend' % args.recipename) | ||
| 77 | with open(appendfile, 'w') as f: | ||
| 78 | f.write('inherit externalsrc\n') | ||
| 79 | f.write('EXTERNALSRC = "%s"\n' % srctree) | ||
| 80 | if initial_rev: | ||
| 81 | f.write('\n# initial_rev: %s\n' % initial_rev) | ||
| 82 | |||
| 83 | _add_md5(config, args.recipename, appendfile) | ||
| 84 | |||
| 85 | return 0 | ||
| 86 | |||
| 87 | |||
| 88 | def _get_recipe_file(cooker, pn): | ||
| 89 | import oe.recipeutils | ||
| 90 | recipefile = oe.recipeutils.pn_to_recipe(cooker, pn) | ||
| 91 | if not recipefile: | ||
| 92 | skipreasons = oe.recipeutils.get_unavailable_reasons(cooker, pn) | ||
| 93 | if skipreasons: | ||
| 94 | logger.error('\n'.join(skipreasons)) | ||
| 95 | else: | ||
| 96 | logger.error("Unable to find any recipe file matching %s" % pn) | ||
| 97 | return recipefile | ||
| 98 | |||
| 99 | |||
| 100 | def extract(args, config, basepath, workspace): | ||
| 101 | import bb | ||
| 102 | import oe.recipeutils | ||
| 103 | |||
| 104 | tinfoil = setup_tinfoil() | ||
| 105 | |||
| 106 | recipefile = _get_recipe_file(tinfoil.cooker, args.recipename) | ||
| 107 | if not recipefile: | ||
| 108 | # Error already logged | ||
| 109 | return -1 | ||
| 110 | rd = oe.recipeutils.parse_recipe(recipefile, tinfoil.config_data) | ||
| 111 | |||
| 112 | srctree = os.path.abspath(args.srctree) | ||
| 113 | initial_rev = _extract_source(srctree, args.keep_temp, args.branch, rd) | ||
| 114 | if initial_rev: | ||
| 115 | return 0 | ||
| 116 | else: | ||
| 117 | return -1 | ||
| 118 | |||
| 119 | |||
| 120 | def _extract_source(srctree, keep_temp, devbranch, d): | ||
| 121 | import bb.event | ||
| 122 | |||
| 123 | def eventfilter(name, handler, event, d): | ||
| 124 | if name == 'base_eventhandler': | ||
| 125 | return True | ||
| 126 | else: | ||
| 127 | return False | ||
| 128 | |||
| 129 | if hasattr(bb.event, 'set_eventfilter'): | ||
| 130 | bb.event.set_eventfilter(eventfilter) | ||
| 131 | |||
| 132 | pn = d.getVar('PN', True) | ||
| 133 | |||
| 134 | if pn == 'perf': | ||
| 135 | logger.error("The perf recipe does not actually check out source and thus cannot be supported by this tool") | ||
| 136 | return None | ||
| 137 | |||
| 138 | if 'work-shared' in d.getVar('S', True): | ||
| 139 | logger.error("The %s recipe uses a shared workdir which this tool does not currently support" % pn) | ||
| 140 | return None | ||
| 141 | |||
| 142 | if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC', True): | ||
| 143 | logger.error("externalsrc is currently enabled for the %s recipe. This prevents the normal do_patch task from working. You will need to disable this first." % pn) | ||
| 144 | return None | ||
| 145 | |||
| 146 | if os.path.exists(srctree): | ||
| 147 | if not os.path.isdir(srctree): | ||
| 148 | logger.error("output path %s exists and is not a directory" % srctree) | ||
| 149 | return None | ||
| 150 | elif os.listdir(srctree): | ||
| 151 | logger.error("output path %s already exists and is non-empty" % srctree) | ||
| 152 | return None | ||
| 153 | |||
| 154 | # Prepare for shutil.move later on | ||
| 155 | bb.utils.mkdirhier(srctree) | ||
| 156 | os.rmdir(srctree) | ||
| 157 | |||
| 158 | initial_rev = None | ||
| 159 | tempdir = tempfile.mkdtemp(prefix='devtool') | ||
| 160 | try: | ||
| 161 | crd = d.createCopy() | ||
| 162 | # Make a subdir so we guard against WORKDIR==S | ||
| 163 | workdir = os.path.join(tempdir, 'workdir') | ||
| 164 | crd.setVar('WORKDIR', workdir) | ||
| 165 | crd.setVar('T', os.path.join(tempdir, 'temp')) | ||
| 166 | |||
| 167 | # FIXME: This is very awkward. Unfortunately it's not currently easy to properly | ||
| 168 | # execute tasks outside of bitbake itself, until then this has to suffice if we | ||
| 169 | # are to handle e.g. linux-yocto's extra tasks | ||
| 170 | executed = [] | ||
| 171 | def exec_task_func(func, report): | ||
| 172 | if not func in executed: | ||
| 173 | deps = crd.getVarFlag(func, 'deps') | ||
| 174 | if deps: | ||
| 175 | for taskdepfunc in deps: | ||
| 176 | exec_task_func(taskdepfunc, True) | ||
| 177 | if report: | ||
| 178 | logger.info('Executing %s...' % func) | ||
| 179 | fn = d.getVar('FILE', True) | ||
| 180 | localdata = bb.build._task_data(fn, func, crd) | ||
| 181 | bb.build.exec_func(func, localdata) | ||
| 182 | executed.append(func) | ||
| 183 | |||
| 184 | logger.info('Fetching %s...' % pn) | ||
| 185 | exec_task_func('do_fetch', False) | ||
| 186 | logger.info('Unpacking...') | ||
| 187 | exec_task_func('do_unpack', False) | ||
| 188 | srcsubdir = crd.getVar('S', True) | ||
| 189 | if srcsubdir != workdir and os.path.dirname(srcsubdir) != workdir: | ||
| 190 | # Handle if S is set to a subdirectory of the source | ||
| 191 | srcsubdir = os.path.join(workdir, os.path.relpath(srcsubdir, workdir).split(os.sep)[0]) | ||
| 192 | |||
| 193 | patchdir = os.path.join(srcsubdir, 'patches') | ||
| 194 | haspatches = False | ||
| 195 | if os.path.exists(patchdir): | ||
| 196 | if os.listdir(patchdir): | ||
| 197 | haspatches = True | ||
| 198 | else: | ||
| 199 | os.rmdir(patchdir) | ||
| 200 | |||
| 201 | if not bb.data.inherits_class('kernel-yocto', d): | ||
| 202 | if not os.listdir(srcsubdir): | ||
| 203 | logger.error("no source unpacked to S, perhaps the %s recipe doesn't use any source?" % pn) | ||
| 204 | return None | ||
| 205 | |||
| 206 | if not os.path.exists(os.path.join(srcsubdir, '.git')): | ||
| 207 | bb.process.run('git init', cwd=srcsubdir) | ||
| 208 | bb.process.run('git add .', cwd=srcsubdir) | ||
| 209 | bb.process.run('git commit -q -m "Initial commit from upstream at version %s"' % crd.getVar('PV', True), cwd=srcsubdir) | ||
| 210 | |||
| 211 | (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srcsubdir) | ||
| 212 | initial_rev = stdout.rstrip() | ||
| 213 | |||
| 214 | bb.process.run('git checkout -b %s' % devbranch, cwd=srcsubdir) | ||
| 215 | bb.process.run('git tag -f devtool-base', cwd=srcsubdir) | ||
| 216 | |||
| 217 | crd.setVar('PATCHTOOL', 'git') | ||
| 218 | |||
| 219 | logger.info('Patching...') | ||
| 220 | exec_task_func('do_patch', False) | ||
| 221 | |||
| 222 | bb.process.run('git tag -f devtool-patched', cwd=srcsubdir) | ||
| 223 | |||
| 224 | if os.path.exists(patchdir): | ||
| 225 | shutil.rmtree(patchdir) | ||
| 226 | if haspatches: | ||
| 227 | bb.process.run('git checkout patches', cwd=srcsubdir) | ||
| 228 | |||
| 229 | shutil.move(srcsubdir, srctree) | ||
| 230 | logger.info('Source tree extracted to %s' % srctree) | ||
| 231 | finally: | ||
| 232 | if keep_temp: | ||
| 233 | logger.info('Preserving temporary directory %s' % tempdir) | ||
| 234 | else: | ||
| 235 | shutil.rmtree(tempdir) | ||
| 236 | return initial_rev | ||
| 237 | |||
| 238 | def _add_md5(config, recipename, filename): | ||
| 239 | import bb.utils | ||
| 240 | md5 = bb.utils.md5_file(filename) | ||
| 241 | with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a') as f: | ||
| 242 | f.write('%s|%s|%s\n' % (recipename, os.path.relpath(filename, config.workspace_path), md5)) | ||
| 243 | |||
| 244 | def _check_preserve(config, recipename): | ||
| 245 | import bb.utils | ||
| 246 | origfile = os.path.join(config.workspace_path, '.devtool_md5') | ||
| 247 | newfile = os.path.join(config.workspace_path, '.devtool_md5_new') | ||
| 248 | preservepath = os.path.join(config.workspace_path, 'attic') | ||
| 249 | with open(origfile, 'r') as f: | ||
| 250 | with open(newfile, 'w') as tf: | ||
| 251 | for line in f.readlines(): | ||
| 252 | splitline = line.rstrip().split('|') | ||
| 253 | if splitline[0] == recipename: | ||
| 254 | removefile = os.path.join(config.workspace_path, splitline[1]) | ||
| 255 | md5 = bb.utils.md5_file(removefile) | ||
| 256 | if splitline[2] != md5: | ||
| 257 | bb.utils.mkdirhier(preservepath) | ||
| 258 | preservefile = os.path.basename(removefile) | ||
| 259 | logger.warn('File %s modified since it was written, preserving in %s' % (preservefile, preservepath)) | ||
| 260 | shutil.move(removefile, os.path.join(preservepath, preservefile)) | ||
| 261 | else: | ||
| 262 | os.remove(removefile) | ||
| 263 | else: | ||
| 264 | tf.write(line) | ||
| 265 | os.rename(newfile, origfile) | ||
| 266 | |||
| 267 | return False | ||
| 268 | |||
| 269 | |||
| 270 | def modify(args, config, basepath, workspace): | ||
| 271 | import bb | ||
| 272 | import oe.recipeutils | ||
| 273 | |||
| 274 | if args.recipename in workspace: | ||
| 275 | logger.error("recipe %s is already in your workspace" % args.recipename) | ||
| 276 | return -1 | ||
| 277 | |||
| 278 | if not args.extract: | ||
| 279 | if not os.path.isdir(args.srctree): | ||
| 280 | logger.error("directory %s does not exist or not a directory (specify -x to extract source from recipe)" % args.srctree) | ||
| 281 | return -1 | ||
| 282 | |||
| 283 | tinfoil = setup_tinfoil() | ||
| 284 | |||
| 285 | recipefile = _get_recipe_file(tinfoil.cooker, args.recipename) | ||
| 286 | if not recipefile: | ||
| 287 | # Error already logged | ||
| 288 | return -1 | ||
| 289 | rd = oe.recipeutils.parse_recipe(recipefile, tinfoil.config_data) | ||
| 290 | |||
| 291 | initial_rev = None | ||
| 292 | commits = [] | ||
| 293 | srctree = os.path.abspath(args.srctree) | ||
| 294 | if args.extract: | ||
| 295 | initial_rev = _extract_source(args.srctree, False, args.branch, rd) | ||
| 296 | if not initial_rev: | ||
| 297 | return -1 | ||
| 298 | # Get list of commits since this revision | ||
| 299 | (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=args.srctree) | ||
| 300 | commits = stdout.split() | ||
| 301 | else: | ||
| 302 | if os.path.exists(os.path.join(args.srctree, '.git')): | ||
| 303 | (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=args.srctree) | ||
| 304 | initial_rev = stdout.rstrip() | ||
| 305 | |||
| 306 | # Handle if S is set to a subdirectory of the source | ||
| 307 | s = rd.getVar('S', True) | ||
| 308 | workdir = rd.getVar('WORKDIR', True) | ||
| 309 | if s != workdir and os.path.dirname(s) != workdir: | ||
| 310 | srcsubdir = os.sep.join(os.path.relpath(s, workdir).split(os.sep)[1:]) | ||
| 311 | srctree = os.path.join(srctree, srcsubdir) | ||
| 312 | |||
| 313 | appendpath = os.path.join(config.workspace_path, 'appends') | ||
| 314 | if not os.path.exists(appendpath): | ||
| 315 | os.makedirs(appendpath) | ||
| 316 | |||
| 317 | appendname = os.path.splitext(os.path.basename(recipefile))[0] | ||
| 318 | if args.wildcard: | ||
| 319 | appendname = re.sub(r'_.*', '_%', appendname) | ||
| 320 | appendfile = os.path.join(appendpath, appendname + '.bbappend') | ||
| 321 | with open(appendfile, 'w') as f: | ||
| 322 | f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n\n') | ||
| 323 | f.write('inherit externalsrc\n') | ||
| 324 | f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n') | ||
| 325 | f.write('EXTERNALSRC_pn-%s = "%s"\n' % (args.recipename, srctree)) | ||
| 326 | if bb.data.inherits_class('autotools-brokensep', rd): | ||
| 327 | logger.info('using source tree as build directory since original recipe inherits autotools-brokensep') | ||
| 328 | f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (args.recipename, srctree)) | ||
| 329 | if initial_rev: | ||
| 330 | f.write('\n# initial_rev: %s\n' % initial_rev) | ||
| 331 | for commit in commits: | ||
| 332 | f.write('# commit: %s\n' % commit) | ||
| 333 | |||
| 334 | _add_md5(config, args.recipename, appendfile) | ||
| 335 | |||
| 336 | logger.info('Recipe %s now set up to build from %s' % (args.recipename, srctree)) | ||
| 337 | |||
| 338 | return 0 | ||
| 339 | |||
| 340 | |||
| 341 | def update_recipe(args, config, basepath, workspace): | ||
| 342 | if not args.recipename in workspace: | ||
| 343 | logger.error("no recipe named %s in your workspace" % args.recipename) | ||
| 344 | return -1 | ||
| 345 | |||
| 346 | # Get initial revision from bbappend | ||
| 347 | appends = glob.glob(os.path.join(config.workspace_path, 'appends', '%s_*.bbappend' % args.recipename)) | ||
| 348 | if not appends: | ||
| 349 | logger.error('unable to find workspace bbappend for recipe %s' % args.recipename) | ||
| 350 | return -1 | ||
| 351 | |||
| 352 | tinfoil = setup_tinfoil() | ||
| 353 | import bb | ||
| 354 | from oe.patch import GitApplyTree | ||
| 355 | import oe.recipeutils | ||
| 356 | |||
| 357 | srctree = workspace[args.recipename] | ||
| 358 | commits = [] | ||
| 359 | update_rev = None | ||
| 360 | if args.initial_rev: | ||
| 361 | initial_rev = args.initial_rev | ||
| 362 | else: | ||
| 363 | initial_rev = None | ||
| 364 | with open(appends[0], 'r') as f: | ||
| 365 | for line in f: | ||
| 366 | if line.startswith('# initial_rev:'): | ||
| 367 | initial_rev = line.split(':')[-1].strip() | ||
| 368 | elif line.startswith('# commit:'): | ||
| 369 | commits.append(line.split(':')[-1].strip()) | ||
| 370 | |||
| 371 | if initial_rev: | ||
| 372 | # Find first actually changed revision | ||
| 373 | (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree) | ||
| 374 | newcommits = stdout.split() | ||
| 375 | for i in xrange(min(len(commits), len(newcommits))): | ||
| 376 | if newcommits[i] == commits[i]: | ||
| 377 | update_rev = commits[i] | ||
| 378 | |||
| 379 | if not initial_rev: | ||
| 380 | logger.error('Unable to find initial revision - please specify it with --initial-rev') | ||
| 381 | return -1 | ||
| 382 | |||
| 383 | if not update_rev: | ||
| 384 | update_rev = initial_rev | ||
| 385 | |||
| 386 | # Find list of existing patches in recipe file | ||
| 387 | recipefile = _get_recipe_file(tinfoil.cooker, args.recipename) | ||
| 388 | if not recipefile: | ||
| 389 | # Error already logged | ||
| 390 | return -1 | ||
| 391 | rd = oe.recipeutils.parse_recipe(recipefile, tinfoil.config_data) | ||
| 392 | existing_patches = oe.recipeutils.get_recipe_patches(rd) | ||
| 393 | |||
| 394 | removepatches = [] | ||
| 395 | if not args.no_remove: | ||
| 396 | # Get all patches from source tree and check if any should be removed | ||
| 397 | tempdir = tempfile.mkdtemp(prefix='devtool') | ||
| 398 | try: | ||
| 399 | GitApplyTree.extractPatches(srctree, initial_rev, tempdir) | ||
| 400 | newpatches = os.listdir(tempdir) | ||
| 401 | for patch in existing_patches: | ||
| 402 | patchfile = os.path.basename(patch) | ||
| 403 | if patchfile not in newpatches: | ||
| 404 | removepatches.append(patch) | ||
| 405 | finally: | ||
| 406 | shutil.rmtree(tempdir) | ||
| 407 | |||
| 408 | # Get updated patches from source tree | ||
| 409 | tempdir = tempfile.mkdtemp(prefix='devtool') | ||
| 410 | try: | ||
| 411 | GitApplyTree.extractPatches(srctree, update_rev, tempdir) | ||
| 412 | |||
| 413 | # Match up and replace existing patches with corresponding new patches | ||
| 414 | updatepatches = False | ||
| 415 | updaterecipe = False | ||
| 416 | newpatches = os.listdir(tempdir) | ||
| 417 | for patch in existing_patches: | ||
| 418 | patchfile = os.path.basename(patch) | ||
| 419 | if patchfile in newpatches: | ||
| 420 | logger.info('Updating patch %s' % patchfile) | ||
| 421 | shutil.move(os.path.join(tempdir, patchfile), patch) | ||
| 422 | newpatches.remove(patchfile) | ||
| 423 | updatepatches = True | ||
| 424 | srcuri = (rd.getVar('SRC_URI', False) or '').split() | ||
| 425 | if newpatches: | ||
| 426 | # Add any patches left over | ||
| 427 | patchdir = os.path.join(os.path.dirname(recipefile), rd.getVar('BPN', True)) | ||
| 428 | bb.utils.mkdirhier(patchdir) | ||
| 429 | for patchfile in newpatches: | ||
| 430 | logger.info('Adding new patch %s' % patchfile) | ||
| 431 | shutil.move(os.path.join(tempdir, patchfile), os.path.join(patchdir, patchfile)) | ||
| 432 | srcuri.append('file://%s' % patchfile) | ||
| 433 | updaterecipe = True | ||
| 434 | if removepatches: | ||
| 435 | # Remove any patches that we don't need | ||
| 436 | for patch in removepatches: | ||
| 437 | patchfile = os.path.basename(patch) | ||
| 438 | for i in xrange(len(srcuri)): | ||
| 439 | if srcuri[i].startswith('file://') and os.path.basename(srcuri[i]).split(';')[0] == patchfile: | ||
| 440 | logger.info('Removing patch %s' % patchfile) | ||
| 441 | srcuri.pop(i) | ||
| 442 | # FIXME "git rm" here would be nice if the file in question is tracked | ||
| 443 | # FIXME there's a chance that this file is referred to by another recipe, in which case deleting wouldn't be the right thing to do | ||
| 444 | os.remove(patch) | ||
| 445 | updaterecipe = True | ||
| 446 | break | ||
| 447 | if updaterecipe: | ||
| 448 | logger.info('Updating recipe %s' % os.path.basename(recipefile)) | ||
| 449 | oe.recipeutils.patch_recipe(rd, recipefile, {'SRC_URI': ' '.join(srcuri)}) | ||
| 450 | elif not updatepatches: | ||
| 451 | # Neither patches nor recipe were updated | ||
| 452 | logger.info('No patches need updating') | ||
| 453 | finally: | ||
| 454 | shutil.rmtree(tempdir) | ||
| 455 | |||
| 456 | return 0 | ||
| 457 | |||
| 458 | |||
| 459 | def status(args, config, basepath, workspace): | ||
| 460 | if workspace: | ||
| 461 | for recipe, value in workspace.iteritems(): | ||
| 462 | print("%s: %s" % (recipe, value)) | ||
| 463 | else: | ||
| 464 | logger.info('No recipes currently in your workspace - you can use "devtool modify" to work on an existing recipe or "devtool add" to add a new one') | ||
| 465 | return 0 | ||
| 466 | |||
| 467 | |||
| 468 | def reset(args, config, basepath, workspace): | ||
| 469 | import bb.utils | ||
| 470 | if not args.recipename in workspace: | ||
| 471 | logger.error("no recipe named %s in your workspace" % args.recipename) | ||
| 472 | return -1 | ||
| 473 | _check_preserve(config, args.recipename) | ||
| 474 | |||
| 475 | preservepath = os.path.join(config.workspace_path, 'attic', args.recipename) | ||
| 476 | def preservedir(origdir): | ||
| 477 | if os.path.exists(origdir): | ||
| 478 | for fn in os.listdir(origdir): | ||
| 479 | logger.warn('Preserving %s in %s' % (fn, preservepath)) | ||
| 480 | bb.utils.mkdirhier(preservepath) | ||
| 481 | shutil.move(os.path.join(origdir, fn), os.path.join(preservepath, fn)) | ||
| 482 | os.rmdir(origdir) | ||
| 483 | |||
| 484 | preservedir(os.path.join(config.workspace_path, 'recipes', args.recipename)) | ||
| 485 | # We don't automatically create this dir next to appends, but the user can | ||
| 486 | preservedir(os.path.join(config.workspace_path, 'appends', args.recipename)) | ||
| 487 | return 0 | ||
| 488 | |||
| 489 | |||
| 490 | def build(args, config, basepath, workspace): | ||
| 491 | import bb | ||
| 492 | if not args.recipename in workspace: | ||
| 493 | logger.error("no recipe named %s in your workspace" % args.recipename) | ||
| 494 | return -1 | ||
| 495 | exec_build_env_command(config.init_path, basepath, 'bitbake -c install %s' % args.recipename, watch=True) | ||
| 496 | |||
| 497 | return 0 | ||
| 498 | |||
| 499 | |||
| 500 | def register_commands(subparsers, context): | ||
| 501 | parser_add = subparsers.add_parser('add', help='Add a new recipe', | ||
| 502 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 503 | parser_add.add_argument('recipename', help='Name for new recipe to add') | ||
| 504 | parser_add.add_argument('srctree', help='Path to external source tree') | ||
| 505 | parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)') | ||
| 506 | parser_add.set_defaults(func=add) | ||
| 507 | |||
| 508 | parser_add = subparsers.add_parser('modify', help='Modify the source for an existing recipe', | ||
| 509 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 510 | parser_add.add_argument('recipename', help='Name for recipe to edit') | ||
| 511 | parser_add.add_argument('srctree', help='Path to external source tree') | ||
| 512 | parser_add.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend') | ||
| 513 | parser_add.add_argument('--extract', '-x', action="store_true", help='Extract source as well') | ||
| 514 | parser_add.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout') | ||
| 515 | parser_add.set_defaults(func=modify) | ||
| 516 | |||
| 517 | parser_add = subparsers.add_parser('extract', help='Extract the source for an existing recipe', | ||
| 518 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 519 | parser_add.add_argument('recipename', help='Name for recipe to extract the source for') | ||
| 520 | parser_add.add_argument('srctree', help='Path to where to extract the source tree') | ||
| 521 | parser_add.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout') | ||
| 522 | parser_add.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)') | ||
| 523 | parser_add.set_defaults(func=extract) | ||
| 524 | |||
| 525 | parser_add = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe', | ||
| 526 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 527 | parser_add.add_argument('recipename', help='Name of recipe to update') | ||
| 528 | parser_add.add_argument('--initial-rev', help='Starting revision for patches') | ||
| 529 | parser_add.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update') | ||
| 530 | parser_add.set_defaults(func=update_recipe) | ||
| 531 | |||
| 532 | parser_status = subparsers.add_parser('status', help='Show status', | ||
| 533 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 534 | parser_status.set_defaults(func=status) | ||
| 535 | |||
| 536 | parser_build = subparsers.add_parser('build', help='Build recipe', | ||
| 537 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 538 | parser_build.add_argument('recipename', help='Recipe to build') | ||
| 539 | parser_build.set_defaults(func=build) | ||
| 540 | |||
| 541 | parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace', | ||
| 542 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 543 | parser_reset.add_argument('recipename', help='Recipe to reset') | ||
| 544 | parser_reset.set_defaults(func=reset) | ||
| 545 | |||
