New devtool finish subcommand
cover New devtool finish subcommand
This patchset adds "devtool finish" that provides a way to easily finish and save your changes to a real layer. Along the way I also fixed some annoyances and bugs in the way "devtool update-recipe" works, since devtool finish makes use of that code.
The following changes since commit da7a2c7b00b40a8759dbe9f4ab6df3e337e3d6b6:
useradd-staticids: use map() instead of imap() (2016-07-12 23:11:57 +0100)
are available in the git repository at:
git://git.openembedded.org/openembedded-core-contrib paule/devtool19-oe
http://cgit.openembedded.org/cgit.cgi/openembedded-core-contrib/log/?h=paule/devtool19-oe
Paul Eggleton (7):
devtool: update-recipe: find and use existing files directories
devtool: update-recipe: don't copy local files that haven't changed lib/oe/recipeutils: fix a few issues in find_layerdir()
devtool: reset: refactor to allow calling separately
devtool: update-recipe: refactor to allow calling separately
devtool: upgrade: record original recipe files
devtool: add finish subcommand
meta/lib/oe/recipeutils.py | 67 ++++++++++- meta/lib/oeqa/selftest/devtool.py | 157 +++++++++++++++++++++++++- scripts/lib/devtool/standard.py | 231 +++++++++++++++++++++++++++++--------- scripts/lib/devtool/upgrade.py | 16 ++- 4 files changed, 410 insertions(+), 61 deletions(-)
Signature · 1 line
2.5.5
PATCH 1/7 devtool: update-recipe: find and use existing files directories
devtool update-recipe was defaulting to the ${BPN} named directory when adding patches next to a recipe, but that meant if you already had files in a ${BP} named directory (i.e. name and version) or "files" then you'd end up with two directories next to the recipe, which is usually not what you want. To avoid this, look through FILESPATH and take the first one that's the same level or one level down from the recipe and already exists, if any.
--- scripts/lib/devtool/standard.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-)
scripts/lib/devtool/standard.py +14 -4
@@ -1039,6 +1039,18 @@ def _export_local_files(srctree, rd, destdir): return (updated, added, removed) +def _determine_files_dir(rd): + """Determine the appropriate files directory for a recipe""" + recipedir = rd.getVar('FILE_DIRNAME', True) + for entry in rd.getVar('FILESPATH', True).split(':'): + relpth = os.path.relpath(entry, recipedir) + if not os.sep in relpth: + # One (or zero) levels below only, so we don't put anything in machine-specific directories + if os.path.isdir(entry): + return entry + return os.path.join(recipedir, rd.getVar('BPN', True)) + + def _update_recipe_srcrev(args, srctree, rd, config_data): """Implement the 'srcrev' mode of update-recipe""" import bb
@@ -1092,8 +1104,7 @@ def _update_recipe_srcrev(args, srctree, rd, config_data): rd, args.append, files, wildcardver=args.wildcard_version, extralines=patchfields, removevalues=removevalues) else: - files_dir = os.path.join(os.path.dirname(recipefile), - rd.getVar('BPN', True)) + files_dir = _determine_files_dir(rd) for basepath, path in upd_f.items(): logger.info('Updating file %s' % basepath) _move_file(os.path.join(local_files_dir, basepath), path)
@@ -1193,8 +1204,7 @@ def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): _move_file(patchfn, path) updatefiles = True # Add any new files - files_dir = os.path.join(os.path.dirname(recipefile), - rd.getVar('BPN', True)) + files_dir = _determine_files_dir(rd) for basepath, path in new_f.items(): logger.info('Adding new file %s' % basepath) _move_file(os.path.join(local_files_dir, basepath),
Signature · 1 line
2.5.5
PATCH 2/7 devtool: update-recipe: don't copy local files that haven't changed
In reply to Paul EggletonIf there are files in the oe-local-files directory which are identical to the original version, then we shouldn't be copying them to the destination layer. This is particularly important when using the -a option to create a bbappend.
--- scripts/lib/devtool/standard.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
scripts/lib/devtool/standard.py +5 -1
@@ -28,6 +28,7 @@ import argparse_oe import scriptutils import errno import glob +import filecmp from collections import OrderedDict from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, DevtoolError from devtool import parse_recipe
@@ -1031,7 +1032,10 @@ def _export_local_files(srctree, rd, destdir): if new_set is not None: for fname in new_set: if fname in existing_files: - updated[fname] = existing_files.pop(fname) + origpath = existing_files.pop(fname) + workpath = os.path.join(local_files_dir, fname) + if not filecmp.cmp(origpath, workpath): + updated[fname] = origpath elif fname != '.gitignore': added[fname] = None
Signature · 1 line
2.5.5
PATCH 3/7 lib/oe/recipeutils: fix a few issues in find_layerdir()
In reply to Paul Eggleton* Allow the function to be called with the base layer path (in which case it will just return the same path)
* Ensure that the function doesn't recurse indefinitely if it's called on a file that's not inside a layer
* Correct the doc comment for accuracy
--- meta/lib/oe/recipeutils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
meta/lib/oe/recipeutils.py +4 -2
@@ -728,14 +728,16 @@ def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, def find_layerdir(fn): - """ Figure out relative path to base of layer for a file (e.g. a recipe)""" - pth = os.path.dirname(fn) + """ Figure out the path to the base of the layer containing a file (e.g. a recipe)""" + pth = fn layerdir = '' while pth: if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')): layerdir = pth break pth = os.path.dirname(pth) + if pth == '/': + return None return layerdir
Signature · 1 line
2.5.5
PATCH 4/7 devtool: reset: refactor to allow calling separately
In reply to Paul EggletonThis will be called by "devtool finish" to allow it to reset the recipe at the end.
--- scripts/lib/devtool/standard.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-)
scripts/lib/devtool/standard.py +23 -17
@@ -1314,24 +1314,10 @@ def status(args, config, basepath, workspace): return 0 -def reset(args, config, basepath, workspace): - """Entry point for the devtool 'reset' subcommand""" - import bb - if args.recipename: - if args.all: - raise DevtoolError("Recipe cannot be specified if -a/--all is used") - else: - for recipe in args.recipename: - check_workspace_recipe(workspace, recipe, checksrc=False) - elif not args.all: - raise DevtoolError("Recipe must be specified, or specify -a/--all to " - "reset all recipes") - if args.all: - recipes = list(workspace.keys()) - else: - recipes = args.recipename +def _reset(recipes, no_clean, config, basepath, workspace): + """Reset one or more recipes""" - if recipes and not args.no_clean: + if recipes and not no_clean: if len(recipes) == 1: logger.info('Cleaning sysroot for recipe %s...' % recipes[0]) else:
@@ -1383,6 +1369,26 @@ def reset(args, config, basepath, workspace): # This is unlikely, but if it's empty we can just remove it os.rmdir(srctree) + +def reset(args, config, basepath, workspace): + """Entry point for the devtool 'reset' subcommand""" + import bb + if args.recipename: + if args.all: + raise DevtoolError("Recipe cannot be specified if -a/--all is used") + else: + for recipe in args.recipename: + check_workspace_recipe(workspace, recipe, checksrc=False) + elif not args.all: + raise DevtoolError("Recipe must be specified, or specify -a/--all to " + "reset all recipes") + if args.all: + recipes = list(workspace.keys()) + else: + recipes = args.recipename + + _reset(recipes, args.no_clean, config, basepath, workspace) + return 0
Signature · 1 line
2.5.5
PATCH 5/7 devtool: update-recipe: refactor to allow calling separately
In reply to Paul EggletonThis will be called by "devtool finish" to allow it to update the recipe or create the bbappend depending on the destination.
--- scripts/lib/devtool/standard.py | 56 +++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 27 deletions(-)
scripts/lib/devtool/standard.py +29 -27
@@ -876,10 +876,10 @@ def _remove_file_entries(srcuri, filelist): break return entries, remaining -def _remove_source_files(args, files, destpath): +def _remove_source_files(append, files, destpath): """Unlink existing patch files""" for path in files: - if args.append: + if append: if not destpath: raise Exception('destpath should be set here') path = os.path.join(destpath, os.path.basename(path))
@@ -1055,7 +1055,7 @@ def _determine_files_dir(rd): return os.path.join(recipedir, rd.getVar('BPN', True)) -def _update_recipe_srcrev(args, srctree, rd, config_data): +def _update_recipe_srcrev(srctree, rd, appendlayerdir, wildcard_version, no_remove): """Implement the 'srcrev' mode of update-recipe""" import bb import oe.recipeutils
@@ -1084,7 +1084,7 @@ def _update_recipe_srcrev(args, srctree, rd, config_data): try: local_files_dir = tempfile.mkdtemp(dir=tempdir) upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir) - if not args.no_remove: + if not no_remove: # Find list of existing patches in recipe file patches_dir = tempfile.mkdtemp(dir=tempdir) old_srcrev = (rd.getVar('SRCREV', False) or '')
@@ -1097,7 +1097,7 @@ def _update_recipe_srcrev(args, srctree, rd, config_data): removedentries = _remove_file_entries(srcuri, remove_files)[0] update_srcuri = True - if args.append: + if appendlayerdir: files = dict((os.path.join(local_files_dir, key), val) for key, val in list(upd_f.items()) + list(new_f.items())) removevalues = {}
@@ -1105,7 +1105,7 @@ def _update_recipe_srcrev(args, srctree, rd, config_data): removevalues = {'SRC_URI': removedentries} patchfields['SRC_URI'] = '\\\n '.join(srcuri) _, destpath = oe.recipeutils.bbappend_recipe( - rd, args.append, files, wildcardver=args.wildcard_version, + rd, appendlayerdir, files, wildcardver=wildcard_version, extralines=patchfields, removevalues=removevalues) else: files_dir = _determine_files_dir(rd)
@@ -1129,21 +1129,21 @@ def _update_recipe_srcrev(args, srctree, rd, config_data): 'point to a git repository where you have pushed your ' 'changes') - _remove_source_files(args, remove_files, destpath) + _remove_source_files(appendlayerdir, remove_files, destpath) return True -def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): +def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, initial_rev): """Implement the 'patch' mode of update-recipe""" import bb import oe.recipeutils recipefile = rd.getVar('FILE', True) - append = workspace[args.recipename]['bbappend'] + append = workspace[recipename]['bbappend'] if not os.path.exists(append): raise DevtoolError('unable to find workspace bbappend for recipe %s' % - args.recipename) + recipename) - initial_rev, update_rev, changed_revs = _get_patchset_revs(srctree, append, args.initial_rev) + initial_rev, update_rev, changed_revs = _get_patchset_revs(srctree, append, initial_rev) if not initial_rev: raise DevtoolError('Unable to find initial revision - please specify ' 'it with --initial-rev')
@@ -1154,7 +1154,7 @@ def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir) remove_files = [] - if not args.no_remove: + if not no_remove: # Get all patches from source tree and check if any should be removed all_patches_dir = tempfile.mkdtemp(dir=tempdir) upd_p, new_p, del_p = _export_patches(srctree, rd, initial_rev,
@@ -1170,7 +1170,7 @@ def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): updaterecipe = False destpath = None srcuri = (rd.getVar('SRC_URI', False) or '').split() - if args.append: + if appendlayerdir: files = dict((os.path.join(local_files_dir, key), val) for key, val in list(upd_f.items()) + list(new_f.items())) files.update(dict((os.path.join(patches_dir, key), val) for
@@ -1185,7 +1185,7 @@ def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): item in remaining] removevalues = {'SRC_URI': removedentries + remaining} _, destpath = oe.recipeutils.bbappend_recipe( - rd, args.append, files, + rd, appendlayerdir, files, removevalues=removevalues) else: logger.info('No patches or local source files needed updating')
@@ -1235,7 +1235,7 @@ def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): finally: shutil.rmtree(tempdir) - _remove_source_files(args, remove_files, destpath) + _remove_source_files(appendlayerdir, remove_files, destpath) return True def _guess_recipe_update_mode(srctree, rdata):
@@ -1260,6 +1260,19 @@ def _guess_recipe_update_mode(srctree, rdata): return 'patch' +def _update_recipe(recipename, workspace, rd, mode, appendlayerdir, wildcard_version, no_remove, initial_rev): + srctree = workspace[recipename]['srctree'] + if mode == 'auto': + mode = _guess_recipe_update_mode(srctree, rd) + + if mode == 'srcrev': + updated = _update_recipe_srcrev(srctree, rd, appendlayerdir, wildcard_version, no_remove) + elif mode == 'patch': + updated = _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, initial_rev) + else: + raise DevtoolError('update_recipe: invalid mode %s' % mode) + return updated + def update_recipe(args, config, basepath, workspace): """Entry point for the devtool 'update-recipe' subcommand""" check_workspace_recipe(workspace, args.recipename)
@@ -1278,18 +1291,7 @@ def update_recipe(args, config, basepath, workspace): if not rd: return 1 - srctree = workspace[args.recipename]['srctree'] - if args.mode == 'auto': - mode = _guess_recipe_update_mode(srctree, rd) - else: - mode = args.mode - - if mode == 'srcrev': - updated = _update_recipe_srcrev(args, srctree, rd, tinfoil.config_data) - elif mode == 'patch': - updated = _update_recipe_patch(args, config, workspace, srctree, rd, tinfoil.config_data) - else: - raise DevtoolError('update_recipe: invalid mode %s' % mode) + updated = _update_recipe(args.recipename, workspace, rd, args.mode, args.append, args.wildcard_version, args.no_remove, args.initial_rev) if updated: rf = rd.getVar('FILE', True)
Signature · 1 line
2.5.5
PATCH 6/7 devtool: upgrade: record original recipe files
In reply to Paul EggletonThis provides us with the information we need to remove the original version recipe and associated files when running "devtool finish" after "devtool upgrade".
--- meta/lib/oe/recipeutils.py | 4 +++- scripts/lib/devtool/upgrade.py | 16 ++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-)
meta/lib/oe/recipeutils.py +3 -1
@@ -365,6 +365,7 @@ def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True): # Copy local files to target directory and gather any remote files bb_dir = os.path.dirname(d.getVar('FILE', True)) + os.sep remotes = [] + copied = [] includes = [path for path in d.getVar('BBINCLUDED', True).split() if path.startswith(bb_dir) and os.path.exists(path)] for path in fetch.localpaths() + includes:
@@ -376,13 +377,14 @@ def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True): if not os.path.exists(subdir): os.makedirs(subdir) shutil.copy2(path, os.path.join(tgt_dir, relpath)) + copied.append(relpath) else: remotes.append(path) # Simply copy whole meta dir, if requested if whole_dir: shutil.copytree(bb_dir, tgt_dir) - return remotes + return copied, remotes def get_recipe_local_files(d, patches=False):
scripts/lib/devtool/upgrade.py +10 -6
@@ -105,7 +105,7 @@ def _rename_recipe_files(oldrecipe, bpn, oldpv, newpv, path): _rename_recipe_dirs(oldpv, newpv, path) return _rename_recipe_file(oldrecipe, bpn, oldpv, newpv, path) -def _write_append(rc, srctree, same_dir, no_same_dir, rev, workspace, d): +def _write_append(rc, srctree, same_dir, no_same_dir, rev, copied, workspace, d): """Writes an append file""" if not os.path.exists(rc): raise DevtoolError("bbappend not created because %s does not exist" % rc)
@@ -128,8 +128,12 @@ def _write_append(rc, srctree, same_dir, no_same_dir, rev, workspace, d): b_is_s = use_external_build(same_dir, no_same_dir, d) if b_is_s: f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree)) + f.write('\n') if rev: - f.write('\n# initial_rev: %s\n' % rev) + f.write('# initial_rev: %s\n' % rev) + if copied: + f.write('# original_path: %s\n' % os.path.dirname(d.getVar('FILE', True))) + f.write('# original_files: %s\n' % ' '.join(copied)) return af def _cleanup_on_error(rf, srctree):
@@ -267,7 +271,7 @@ def _create_new_recipe(newpv, md5, sha256, srcrev, srcbranch, workspace, tinfoil bpn = rd.getVar('BPN', True) path = os.path.join(workspace, 'recipes', bpn) bb.utils.mkdirhier(path) - oe.recipeutils.copy_recipe_files(rd, path) + copied, _ = oe.recipeutils.copy_recipe_files(rd, path) oldpv = rd.getVar('PV', True) if not newpv:
@@ -317,7 +321,7 @@ def _create_new_recipe(newpv, md5, sha256, srcrev, srcbranch, workspace, tinfoil rd = oe.recipeutils.parse_recipe(fullpath, None, tinfoil.config_data) oe.recipeutils.patch_recipe(rd, fullpath, newvalues) - return fullpath + return fullpath, copied def upgrade(args, config, basepath, workspace): """Entry point for the devtool 'upgrade' subcommand"""
@@ -360,7 +364,7 @@ def upgrade(args, config, basepath, workspace): rev2, md5, sha256 = _extract_new_source(args.version, srctree, args.no_patch, args.srcrev, args.branch, args.keep_temp, tinfoil, rd) - rf = _create_new_recipe(args.version, md5, sha256, args.srcrev, args.srcbranch, config.workspace_path, tinfoil, rd) + rf, copied = _create_new_recipe(args.version, md5, sha256, args.srcrev, args.srcbranch, config.workspace_path, tinfoil, rd) except bb.process.CmdError as e: _upgrade_error(e, rf, srctree) except DevtoolError as e:
@@ -368,7 +372,7 @@ def upgrade(args, config, basepath, workspace): standard._add_md5(config, pn, os.path.dirname(rf)) af = _write_append(rf, srctree, args.same_dir, args.no_same_dir, rev2, - config.workspace_path, rd) + copied, config.workspace_path, rd) standard._add_md5(config, pn, af) logger.info('Upgraded source extracted to %s' % srctree) logger.info('New recipe is %s' % rf)
Signature · 1 line
2.5.5
PATCH 7/7 devtool: add finish subcommand
In reply to Paul EggletonAdd a subcommand which will "finish" the work on a recipe. This is effectively the same as update-recipe followed by reset, except that the destination layer is required and it will do the right thing depending on the situation - if the recipe file itself is in the workspace (e.g. as a result of devtool add), the recipe file and any associated files will be moved to the destination layer; or if the destination layer is the one containing the original recipe, the recipe will be overwritten; otherwise a bbappend will be created to apply the changes. In all cases the layer path can be loosely specified - it could be a layer name, or a partial path into a recipe. In the case of upgrades, devtool finish will also take care of deleting the old recipe.
This avoids the user having to figure out the correct actions when they're done - they just do "devtool finish recipename layername" and it saves their work and then removes the recipe from the workspace.
Addresses [YOCTO #8594].
--- meta/lib/oe/recipeutils.py | 57 +++++++++++++- meta/lib/oeqa/selftest/devtool.py | 157 +++++++++++++++++++++++++++++++++++++- scripts/lib/devtool/standard.py | 111 ++++++++++++++++++++++++++- 3 files changed, 322 insertions(+), 3 deletions(-)
meta/lib/oe/recipeutils.py +56 -1
@@ -2,7 +2,7 @@ # # Some code borrowed from the OE layer index # -# Copyright (C) 2013-2015 Intel Corporation +# Copyright (C) 2013-2016 Intel Corporation # import sys
@@ -15,6 +15,7 @@ from . import utils import shutil import re import fnmatch +import glob from collections import OrderedDict, defaultdict
@@ -450,6 +451,60 @@ def validate_pn(pn): return '' +def get_bbfile_path(d, destdir, extrapathhint=None): + """ + Determine the correct path for a recipe within a layer + Parameters: + d: Recipe-specific datastore + destdir: destination directory. Can be the path to the base of the layer or a + partial path somewhere within the layer. + extrapathhint: a path relative to the base of the layer to try + """ + import bb.cookerdata + + destdir = os.path.abspath(destdir) + destlayerdir = find_layerdir(destdir) + + # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf + confdata = d.createCopy() + confdata.setVar('BBFILES', '') + confdata.setVar('LAYERDIR', destlayerdir) + destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf") + confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata) + pn = d.getVar('PN', True) + + bbfilespecs = (confdata.getVar('BBFILES', True) or '').split() + if destdir == destlayerdir: + for bbfilespec in bbfilespecs: + if not bbfilespec.endswith('.bbappend'): + for match in glob.glob(bbfilespec): + splitext = os.path.splitext(os.path.basename(match)) + if splitext[1] == '.bb': + mpn = splitext[0].split('_')[0] + if mpn == pn: + return os.path.dirname(match) + + # Try to make up a path that matches BBFILES + # this is a little crude, but better than nothing + bpn = d.getVar('BPN', True) + recipefn = os.path.basename(d.getVar('FILE', True)) + pathoptions = [destdir] + if extrapathhint: + pathoptions.append(os.path.join(destdir, extrapathhint)) + if destdir == destlayerdir: + pathoptions.append(os.path.join(destdir, 'recipes-%s' % bpn, bpn)) + pathoptions.append(os.path.join(destdir, 'recipes', bpn)) + pathoptions.append(os.path.join(destdir, bpn)) + elif not destdir.endswith(('/' + pn, '/' + bpn)): + pathoptions.append(os.path.join(destdir, bpn)) + closepath = '' + for pathoption in pathoptions: + bbfilepath = os.path.join(pathoption, 'test.bb') + for bbfilespec in bbfilespecs: + if fnmatch.fnmatchcase(bbfilepath, bbfilespec): + return pathoption + return None + def get_bbappend_path(d, destlayerdir, wildcardver=False): """Determine how a bbappend for a recipe should be named and located within another layer"""
meta/lib/oeqa/selftest/devtool.py +156 -1
169 lines not on this page · raw message
scripts/lib/devtool/standard.py +110 -1
125 lines not on this page · raw message
Signature · 1 line
2.5.5