devtool / recipetool improvements
cover devtool / recipetool improvements
More devtool / recipetool work - some improvements to the help output, behaviour of (un)deploy-target, extensibility of autoconf/cmake handling, and improvements to cmake dependency extraction, plus one or two other minor fixes.
The following changes since commit 58b45240739da210ef2fecea931c8b8daa9a4c07:
epiphany: Add libxml2-native to DEPENDS (2016-02-18 22:55:12 +0000)
are available in the git repository at:
git://git.openembedded.org/openembedded-core-contrib paule/devtool14-oe
http://cgit.openembedded.org/cgit.cgi/openembedded-core-contrib/log/?h=paule/devtool14-oe
Paul Eggleton (14):
devtool: minor fix for error message
devtool / recipetool: use common code for launching editor
devtool: reset: fix preserving patches/other files next to recipes
devtool: update-recipe: don't show workspace recipe warning if no update
devtool: categorise and order subcommands in help output scripts/lib/argparse_oe: tweak title above options
devtool: (un)deploy-target: add help descriptions
devtool: sdk-update: tweak command-line handling of updateserver
devtool: deploy-target: write deployed files list to target
devtool: undeploy-target: support undeploying all recipes
devtool: deploy-target: preserve existing files
devtool: modify: tweak help description for behaviour change
recipetool: create: add additional extension mechanisms
recipetool: create: improve CMake package mapping
scripts/devtool | 10 +- scripts/lib/argparse_oe.py | 63 ++++++ scripts/lib/devtool/build-image.py | 3 +- scripts/lib/devtool/build.py | 3 +- scripts/lib/devtool/deploy.py | 248 ++++++++++++++++++----- scripts/lib/devtool/package.py | 5 +- scripts/lib/devtool/runqemu.py | 3 +- scripts/lib/devtool/sdk.py | 19 +- scripts/lib/devtool/search.py | 3 +- scripts/lib/devtool/standard.py | 47 +++-- scripts/lib/devtool/upgrade.py | 3 +- scripts/lib/devtool/utilcmds.py | 19 +- scripts/lib/recipetool/create.py | 15 +- scripts/lib/recipetool/create_buildsys.py | 154 ++++++++++++-- scripts/lib/recipetool/create_buildsys_python.py | 2 +- scripts/lib/recipetool/newappend.py | 8 +- scripts/lib/scriptutils.py | 15 ++ 17 files changed, 505 insertions(+), 115 deletions(-)
Signature · 1 line
2.5.0
PATCH 1/14 devtool: minor fix for error message
There is no -N/--name option for devtool, that's a recipetool option - with devtool you just specify the name as a positional argument.
--- scripts/lib/devtool/standard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
scripts/lib/devtool/standard.py +1 -1
@@ -150,7 +150,7 @@ def add(args, config, basepath, workspace): stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create -o %s "%s" %s' % (color, tempdir, source, extracmdopts)) except bb.process.ExecutionError as e: if e.exitcode == 15: - raise DevtoolError('Unable to auto-determine name from source tree, please specify it with -N/--name') + raise DevtoolError('Unable to auto-determine recipe name from source tree, please specify it on the command line') else: raise DevtoolError('Command \'%s\' failed:\n%s' % (e.command, e.stdout))
Signature · 1 line
2.5.0
PATCH 2/14 devtool / recipetool: use common code for launching editor
In reply to Paul EggletonLooking at Chris Larson's code for starting the user's editor for "recipetool newappend" it was slightly better than what I wrote for "devtool edit-recipe" in that it checks VISUAL as well as EDITOR and defaults to vi if neither are set, so break this out to its own function and call it from both places. The broken out version passes shell=True however in case it's a more complicated command rather than just a name of an executable.
--- scripts/lib/devtool/utilcmds.py | 13 ++----------- scripts/lib/recipetool/newappend.py | 8 ++------ scripts/lib/scriptutils.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 17 deletions(-)
scripts/lib/devtool/utilcmds.py +2 -11
@@ -24,6 +24,7 @@ import tempfile import logging import argparse import subprocess +import scriptutils from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, DevtoolError from devtool import parse_recipe
@@ -48,17 +49,7 @@ def edit_recipe(args, config, basepath, workspace): raise DevtoolError("Recipe file for %s is not under the workspace" % args.recipename) - editor = os.environ.get('EDITOR', None) - if not editor: - raise DevtoolError("EDITOR environment variable not set") - - import subprocess - try: - subprocess.check_call('%s "%s"' % (editor, recipefile), shell=True) - except subprocess.CalledProcessError as e: - return e.returncode - - return 0 + return scriptutils.run_editor(recipefile) def configure_help(args, config, basepath, workspace):
scripts/lib/recipetool/newappend.py +2 -6
@@ -27,6 +27,7 @@ import os import re import subprocess import sys +import scriptutils logger = logging.getLogger('recipetool')
@@ -96,12 +97,7 @@ def newappend(args): return 1 if args.edit: - editor = os.getenv('VISUAL', os.getenv('EDITOR', 'vi')) - try: - return subprocess.check_call([editor, append_path, recipe_path]) - except OSError as exc: - logger.error("Execution of editor '%s' failed: %s", editor, exc) - return 1 + return scriptutils.run_editor([append_path, recipe_path]) else: print(append_path)
scripts/lib/scriptutils.py +15 -0
@@ -20,6 +20,7 @@ import os import logging import glob import argparse +import subprocess def logger_create(name): logger = logging.getLogger(name)
@@ -101,3 +102,17 @@ def fetch_uri(d, uri, destdir, srcrev=None): os.chdir(olddir) return ret +def run_editor(fn): + if isinstance(fn, basestring): + params = '"%s"' % fn + else: + params = '' + for fnitem in fn: + params += ' "%s"' % fnitem + + editor = os.getenv('VISUAL', os.getenv('EDITOR', 'vi')) + try: + return subprocess.check_call('%s %s' % (editor, params), shell=True) + except OSError as exc: + logger.error("Execution of editor '%s' failed: %s", editor, exc) + return 1
Signature · 1 line
2.5.0
PATCH 3/14 devtool: reset: fix preserving patches/other files next to recipes
In reply to Paul EggletonIf files had been created next to the recipe (for example devtool add, edit the source and commit and then devtool update-recipe), running devtool reset failed to preserve those files and gave an error due to trying to rmdir the directory containing them which wasn't empty. Fix the preservation of files in the "attic" directory properly so we catch anything under the directory for the recipe, and replicate the same structure in the attic directory rather than slightly flattening it as we were before.
--- scripts/lib/devtool/standard.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
scripts/lib/devtool/standard.py +4 -4
@@ -190,7 +190,7 @@ def add(args, config, basepath, workspace): shutil.move(recipes[0], recipefile) else: raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout)) - attic_recipe = os.path.join(config.workspace_path, 'attic', os.path.basename(recipefile)) + attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile)) if os.path.exists(attic_recipe): logger.warn('A modified recipe from a previous invocation exists in %s - you may wish to move this over the top of the new recipe if you had changes in it that you want to continue with' % attic_recipe) finally:
@@ -645,7 +645,7 @@ def _check_preserve(config, recipename): import bb.utils origfile = os.path.join(config.workspace_path, '.devtool_md5') newfile = os.path.join(config.workspace_path, '.devtool_md5_new') - preservepath = os.path.join(config.workspace_path, 'attic') + preservepath = os.path.join(config.workspace_path, 'attic', recipename) with open(origfile, 'r') as f: with open(newfile, 'w') as tf: for line in f.readlines():
@@ -1256,7 +1256,7 @@ def reset(args, config, basepath, workspace): for pn in recipes: _check_preserve(config, pn) - preservepath = os.path.join(config.workspace_path, 'attic', pn) + preservepath = os.path.join(config.workspace_path, 'attic', pn, pn) def preservedir(origdir): if os.path.exists(origdir): for root, dirs, files in os.walk(origdir):
@@ -1265,7 +1265,7 @@ def reset(args, config, basepath, workspace): _move_file(os.path.join(origdir, fn), os.path.join(preservepath, fn)) for dn in dirs: - os.rmdir(os.path.join(root, dn)) + preservedir(os.path.join(root, dn)) os.rmdir(origdir) preservedir(os.path.join(config.workspace_path, 'recipes', pn))
Signature · 1 line
2.5.0
PATCH 4/14 devtool: update-recipe: don't show workspace recipe warning if no update
In reply to Paul EggletonIf we didn't make any changes to the file then there's no point warning the user that we have done.
--- scripts/lib/devtool/standard.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
scripts/lib/devtool/standard.py +9 -5
@@ -1034,6 +1034,7 @@ def _update_recipe_srcrev(args, srctree, rd, config_data): 'changes') _remove_source_files(args, remove_files, destpath) + return True def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): """Implement the 'patch' mode of update-recipe"""
@@ -1135,10 +1136,12 @@ def _update_recipe_patch(args, config, workspace, srctree, rd, config_data): elif not updatefiles: # Neither patches nor recipe were updated logger.info('No patches or files need updating') + return False finally: shutil.rmtree(tempdir) _remove_source_files(args, remove_files, destpath) + return True def _guess_recipe_update_mode(srctree, rdata): """Guess the recipe update mode to use"""
@@ -1187,15 +1190,16 @@ def update_recipe(args, config, basepath, workspace): mode = args.mode if mode == 'srcrev': - _update_recipe_srcrev(args, srctree, rd, tinfoil.config_data) + updated = _update_recipe_srcrev(args, srctree, rd, tinfoil.config_data) elif mode == 'patch': - _update_recipe_patch(args, config, workspace, srctree, rd, tinfoil.config_data) + updated = _update_recipe_patch(args, config, workspace, srctree, rd, tinfoil.config_data) else: raise DevtoolError('update_recipe: invalid mode %s' % mode) - rf = rd.getVar('FILE', True) - if rf.startswith(config.workspace_path): - logger.warn('Recipe file %s has been updated but is inside the workspace - you will need to move it (and any associated files next to it) out to the desired layer before using "devtool reset" in order to keep any changes' % rf) + if updated: + rf = rd.getVar('FILE', True) + if rf.startswith(config.workspace_path): + logger.warn('Recipe file %s has been updated but is inside the workspace - you will need to move it (and any associated files next to it) out to the desired layer before using "devtool reset" in order to keep any changes' % rf) return 0
Signature · 1 line
2.5.0
PATCH 5/14 devtool: categorise and order subcommands in help output
In reply to Paul EggletonThe listing of subcommands in the --help output for devtool was starting to get difficult to follow, with commands appearing in no particular order (due to some being in separate modules and the order of those modules being parsed). Logically grouping the subcommands as well as being able to exercise some control over the order of the subcommands and groups would help, if we do so without losing the dynamic nature of the list (i.e. that it comes from the plugins). Argparse provides no built-in way to handle this and really, really makes it a pain to add, but with some subclassing and hacking it's now possible, and can be extended by any plugin as desired.
To put a subcommand into a group, all you need to do is specify a group= parameter in the call to subparsers.add_parser(). you can also specify an order= parameter to make the subcommand sort higher or lower in the list (higher order numbers appear first, so use negative numbers to force items to the end if that's what you want). To add a new group, use subparsers.add_subparser_group(), supplying the name, description and optionally an order number for the group itself (again, higher numbers appear first).
--- scripts/devtool | 10 ++++++- scripts/lib/argparse_oe.py | 59 ++++++++++++++++++++++++++++++++++++++ scripts/lib/devtool/build-image.py | 3 +- scripts/lib/devtool/build.py | 3 +- scripts/lib/devtool/deploy.py | 8 ++++-- scripts/lib/devtool/package.py | 5 +++- scripts/lib/devtool/runqemu.py | 3 +- scripts/lib/devtool/sdk.py | 10 +++++-- scripts/lib/devtool/search.py | 3 +- scripts/lib/devtool/standard.py | 21 +++++++++----- scripts/lib/devtool/upgrade.py | 3 +- scripts/lib/devtool/utilcmds.py | 6 ++-- 12 files changed, 114 insertions(+), 20 deletions(-)
scripts/devtool +9 -1
@@ -271,10 +271,18 @@ def main(): subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>') + subparsers.add_subparser_group('sdk', 'SDK maintenance', -2) + subparsers.add_subparser_group('advanced', 'Advanced', -1) + subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100) + subparsers.add_subparser_group('info', 'Getting information') + subparsers.add_subparser_group('working', 'Working on a recipe in the workspace') + subparsers.add_subparser_group('testbuild', 'Testing changes on target') + if not context.fixed_setup: parser_create_workspace = subparsers.add_parser('create-workspace', help='Set up workspace in an alternative location', - description='Sets up a new workspace. NOTE: other devtool subcommands will create a workspace automatically as needed, so you only need to use %(prog)s if you want to specify where the workspace should be located.') + description='Sets up a new workspace. NOTE: other devtool subcommands will create a workspace automatically as needed, so you only need to use %(prog)s if you want to specify where the workspace should be located.', + group='advanced') parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created') parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration') parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True)
scripts/lib/argparse_oe.py +59 -0
83 lines not on this page · raw message
scripts/lib/devtool/build-image.py +2 -1
@@ -109,7 +109,8 @@ def register_commands(subparsers, context): parser = subparsers.add_parser('build-image', help='Build image including workspace recipe packages', description='Builds an image, extending it to include ' - 'packages from recipes in the workspace') + 'packages from recipes in the workspace', + group='testbuild', order=-10) parser.add_argument('imagename', help='Image recipe to build', nargs='?') parser.add_argument('-p', '--add-packages', help='Instead of adding packages for the ' 'entire workspace, specify packages to be added to the image '
scripts/lib/devtool/build.py +2 -1
@@ -79,7 +79,8 @@ def build(args, config, basepath, workspace): def register_commands(subparsers, context): """Register devtool subcommands from this plugin""" parser_build = subparsers.add_parser('build', help='Build a recipe', - description='Builds the specified recipe using bitbake (up to and including %s)' % ', '.join(_get_build_tasks(context.config))) + description='Builds the specified recipe using bitbake (up to and including %s)' % ', '.join(_get_build_tasks(context.config)), + group='working') parser_build.add_argument('recipename', help='Recipe to build') parser_build.add_argument('-s', '--disable-parallel-make', action="store_true", help='Disable make parallelism') parser_build.set_defaults(func=build)
scripts/lib/devtool/deploy.py +6 -2
@@ -131,7 +131,9 @@ def undeploy(args, config, basepath, workspace): def register_commands(subparsers, context): """Register devtool subcommands from the deploy plugin""" - parser_deploy = subparsers.add_parser('deploy-target', help='Deploy recipe output files to live target machine') + parser_deploy = subparsers.add_parser('deploy-target', + help='Deploy recipe output files to live target machine', + group='testbuild') parser_deploy.add_argument('recipename', help='Recipe to deploy') parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]') parser_deploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true')
@@ -139,7 +141,9 @@ def register_commands(subparsers, context): parser_deploy.add_argument('-n', '--dry-run', help='List files to be deployed only', action='store_true') parser_deploy.set_defaults(func=deploy) - parser_undeploy = subparsers.add_parser('undeploy-target', help='Undeploy recipe output files in live target machine') + parser_undeploy = subparsers.add_parser('undeploy-target', + help='Undeploy recipe output files in live target machine', + group='testbuild') parser_undeploy.add_argument('recipename', help='Recipe to undeploy') parser_undeploy.add_argument('target', help='Live target machine running an ssh server: user@hostname') parser_undeploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true')
scripts/lib/devtool/package.py +4 -1
@@ -54,6 +54,9 @@ def package(args, config, basepath, workspace): def register_commands(subparsers, context): """Register devtool subcommands from the package plugin""" if context.fixed_setup: - parser_package = subparsers.add_parser('package', help='Build packages for a recipe', description='Builds packages for a recipe\'s output files') + parser_package = subparsers.add_parser('package', + help='Build packages for a recipe', + description='Builds packages for a recipe\'s output files', + group='testbuild', order=-5) parser_package.add_argument('recipename', help='Recipe to package') parser_package.set_defaults(func=package)
scripts/lib/devtool/runqemu.py +2 -1
@@ -57,7 +57,8 @@ def register_commands(subparsers, context): """Register devtool subcommands from this plugin""" if context.fixed_setup: parser_runqemu = subparsers.add_parser('runqemu', help='Run QEMU on the specified image', - description='Runs QEMU to boot the specified image') + description='Runs QEMU to boot the specified image', + group='testbuild', order=-20) parser_runqemu.add_argument('imagename', help='Name of built image to boot within QEMU', nargs='?') parser_runqemu.add_argument('args', help='Any remaining arguments are passed to the runqemu script (pass --help after imagename to see what these are)', nargs=argparse.REMAINDER)
scripts/lib/devtool/sdk.py +8 -2
@@ -296,10 +296,16 @@ def sdk_install(args, config, basepath, workspace): def register_commands(subparsers, context): """Register devtool subcommands from the sdk plugin""" if context.fixed_setup: - parser_sdk = subparsers.add_parser('sdk-update', help='Update SDK components from a nominated location') + parser_sdk = subparsers.add_parser('sdk-update', + help='Update SDK components from a nominated location', + group='sdk') parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from', nargs='?') parser_sdk.add_argument('--skip-prepare', action="store_true", help='Skip re-preparing the build system after updating (for debugging only)') parser_sdk.set_defaults(func=sdk_update) - parser_sdk_install = subparsers.add_parser('sdk-install', help='Install additional SDK components', description='Installs additional recipe development files into the SDK. (You can use "devtool search" to find available recipes.)') + + parser_sdk_install = subparsers.add_parser('sdk-install', + help='Install additional SDK components', + description='Installs additional recipe development files into the SDK. (You can use "devtool search" to find available recipes.)', + group='sdk') parser_sdk_install.add_argument('recipename', help='Name of the recipe to install the development artifacts for', nargs='+') parser_sdk_install.set_defaults(func=sdk_install)
scripts/lib/devtool/search.py +2 -1
@@ -82,6 +82,7 @@ def search(args, config, basepath, workspace): def register_commands(subparsers, context): """Register devtool subcommands from this plugin""" parser_search = subparsers.add_parser('search', help='Search available recipes', - description='Searches for available target recipes. Matches on recipe name, package name, description and installed files, and prints the recipe name on match.') + description='Searches for available target recipes. Matches on recipe name, package name, description and installed files, and prints the recipe name on match.', + group='info') parser_search.add_argument('keyword', help='Keyword to search for (regular expression syntax allowed)') parser_search.set_defaults(func=search, no_workspace=True)
scripts/lib/devtool/standard.py +14 -7
@@ -1303,7 +1303,8 @@ def register_commands(subparsers, context): defsrctree = get_default_srctree(context.config) parser_add = subparsers.add_parser('add', help='Add a new recipe', - description='Adds a new recipe to the workspace to build a specified source tree. Can optionally fetch a remote URI and unpack it to create the source tree.') + description='Adds a new recipe to the workspace to build a specified source tree. Can optionally fetch a remote URI and unpack it to create the source tree.', + group='starting', order=100) parser_add.add_argument('recipename', nargs='?', help='Name for new recipe to add (just name - no version, path or extension). If not specified, will attempt to auto-detect it.') parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree) parser_add.add_argument('fetchuri', nargs='?', help='Fetch the specified URI and extract it to create the source tree')
@@ -1319,7 +1320,8 @@ def register_commands(subparsers, context): parser_add.set_defaults(func=add) parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe', - description='Enables modifying the source for an existing recipe. You can either provide your own pre-prepared source tree, or specify -x/--extract to extract the source being fetched by the recipe.') + description='Enables modifying the source for an existing recipe. You can either provide your own pre-prepared source tree, or specify -x/--extract to extract the source being fetched by the recipe.', + group='starting', order=90) parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)') parser_modify.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree) parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
@@ -1333,7 +1335,8 @@ def register_commands(subparsers, context): parser_modify.set_defaults(func=modify) parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe', - description='Extracts the source for an existing recipe') + description='Extracts the source for an existing recipe', + group='advanced') parser_extract.add_argument('recipename', help='Name of recipe to extract the source for') parser_extract.add_argument('srctree', help='Path to where to extract the source tree') parser_extract.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (default "%(default)s")')
@@ -1342,7 +1345,8 @@ def register_commands(subparsers, context): parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe', description='Synchronize the previously extracted source tree for an existing recipe', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + group='advanced') parser_sync.add_argument('recipename', help='Name of recipe to sync the source for') parser_sync.add_argument('srctree', help='Path to the source tree') parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
@@ -1350,7 +1354,8 @@ def register_commands(subparsers, context): parser_sync.set_defaults(func=sync) parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe', - description='Applies changes from external source tree to a recipe (updating/adding/removing patches as necessary, or by updating SRCREV). Note that these changes need to have been committed to the git repository in order to be recognised.') + description='Applies changes from external source tree to a recipe (updating/adding/removing patches as necessary, or by updating SRCREV). Note that these changes need to have been committed to the git repository in order to be recognised.', + group='working', order=-90) parser_update_recipe.add_argument('recipename', help='Name of recipe to update') parser_update_recipe.add_argument('--mode', '-m', choices=['patch', 'srcrev', 'auto'], default='auto', help='Update mode (where %(metavar)s is %(choices)s; default is %(default)s)', metavar='MODE') parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
@@ -1360,11 +1365,13 @@ def register_commands(subparsers, context): parser_update_recipe.set_defaults(func=update_recipe) parser_status = subparsers.add_parser('status', help='Show workspace status', - description='Lists recipes currently in your workspace and the paths to their respective external source trees') + description='Lists recipes currently in your workspace and the paths to their respective external source trees', + group='info', order=100) parser_status.set_defaults(func=status) parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace', - description='Removes the specified recipe from your workspace (resetting its state)') + description='Removes the specified recipe from your workspace (resetting its state)', + group='working', order=-100) parser_reset.add_argument('recipename', nargs='?', help='Recipe to reset') parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)') parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
scripts/lib/devtool/upgrade.py +2 -1
@@ -339,7 +339,8 @@ def upgrade(args, config, basepath, workspace): def register_commands(subparsers, context): """Register devtool subcommands from this plugin""" parser_upgrade = subparsers.add_parser('upgrade', help='Upgrade an existing recipe', - description='Upgrades an existing recipe to a new upstream version. Puts the upgraded recipe file into the workspace along with any associated files, and extracts the source tree to a specified location (in case patches need rebasing or adding to as a result of the upgrade).') + description='Upgrades an existing recipe to a new upstream version. Puts the upgraded recipe file into the workspace along with any associated files, and extracts the source tree to a specified location (in case patches need rebasing or adding to as a result of the upgrade).', + group='starting') parser_upgrade.add_argument('recipename', help='Name of recipe to upgrade (just name - no version, path or extension)') parser_upgrade.add_argument('srctree', help='Path to where to extract the source tree') parser_upgrade.add_argument('--version', '-V', help='Version to upgrade to (PV)')
scripts/lib/devtool/utilcmds.py +4 -2
@@ -214,7 +214,8 @@ The ./configure %s output for %s follows. def register_commands(subparsers, context): """Register devtool subcommands from this plugin""" parser_edit_recipe = subparsers.add_parser('edit-recipe', help='Edit a recipe file in your workspace', - description='Runs the default editor (as specified by the EDITOR variable) on the specified recipe. Note that the recipe file itself must be in the workspace (i.e. as a result of "devtool add" or "devtool upgrade"); you can override this with the -a/--any-recipe option.') + description='Runs the default editor (as specified by the EDITOR variable) on the specified recipe. Note that the recipe file itself must be in the workspace (i.e. as a result of "devtool add" or "devtool upgrade"); you can override this with the -a/--any-recipe option.', + group='working') parser_edit_recipe.add_argument('recipename', help='Recipe to edit') parser_edit_recipe.add_argument('--any-recipe', '-a', action="store_true", help='Edit any recipe, not just where the recipe file itself is in the workspace') parser_edit_recipe.set_defaults(func=edit_recipe)
@@ -223,7 +224,8 @@ def register_commands(subparsers, context): # gets the order wrong - recipename must come before --arg parser_configure_help = subparsers.add_parser('configure-help', help='Get help on configure script options', usage='devtool configure-help [options] recipename [--arg ...]', - description='Displays the help for the configure script for the specified recipe (i.e. runs ./configure --help) prefaced by a header describing the current options being specified. Output is piped through less (or whatever PAGER is set to, if set) for easy browsing.') + description='Displays the help for the configure script for the specified recipe (i.e. runs ./configure --help) prefaced by a header describing the current options being specified. Output is piped through less (or whatever PAGER is set to, if set) for easy browsing.', + group='working') parser_configure_help.add_argument('recipename', help='Recipe to show configure help for') parser_configure_help.add_argument('-p', '--no-pager', help='Disable paged output', action="store_true") parser_configure_help.add_argument('-n', '--no-header', help='Disable explanatory header text', action="store_true")
Signature · 1 line
2.5.0
PATCH 6/14 scripts/lib/argparse_oe: tweak title above options
In reply to Paul EggletonNaming these as "optional arguments" is perhaps slightly confusing since some of the positional arguments might also be optional; in addition it's rare (though possible) for options to be mandatory - up until recently we had a recipetool option (-o) that was mandatory. It's not perfect, but change it to "options" so it's at least a bit more appropriate.
--- scripts/lib/argparse_oe.py | 4 ++++ 1 file changed, 4 insertions(+)
scripts/lib/argparse_oe.py +4 -0
@@ -51,6 +51,10 @@ class ArgumentSubParser(ArgumentParser): if 'order' in kwargs: self._order = kwargs.pop('order') super(ArgumentSubParser, self).__init__(*args, **kwargs) + for agroup in self._action_groups: + if agroup.title == 'optional arguments': + agroup.title = 'options' + break def parse_known_args(self, args=None, namespace=None): # This works around argparse not handling optional positional arguments being
Signature · 1 line
2.5.0
PATCH 7/14 devtool: (un)deploy-target: add help descriptions
In reply to Paul EggletonAdd a long description used when running --help on the specific command.
--- scripts/lib/devtool/deploy.py | 2 ++ 1 file changed, 2 insertions(+)
scripts/lib/devtool/deploy.py +2 -0
@@ -133,6 +133,7 @@ def register_commands(subparsers, context): """Register devtool subcommands from the deploy plugin""" parser_deploy = subparsers.add_parser('deploy-target', help='Deploy recipe output files to live target machine', + description='Deploys a recipe\'s build output (i.e. the output of the do_install task) to a live target machine over ssh. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', group='testbuild') parser_deploy.add_argument('recipename', help='Recipe to deploy') parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]')
@@ -143,6 +144,7 @@ def register_commands(subparsers, context): parser_undeploy = subparsers.add_parser('undeploy-target', help='Undeploy recipe output files in live target machine', + description='Un-deploys recipe output files previously deployed to a live target machine by devtool deploy-target.', group='testbuild') parser_undeploy.add_argument('recipename', help='Recipe to undeploy') parser_undeploy.add_argument('target', help='Live target machine running an ssh server: user@hostname')
Signature · 1 line
2.5.0
PATCH 8/14 devtool: sdk-update: tweak command-line handling of updateserver
In reply to Paul EggletonGet the default value for updateserver from the configuration file and show it in the help; also only make the parameter optional if it's specified. This means we can also drop the check in the function as argparse will then ensure it's specified if there's no config setting.
--- scripts/lib/devtool/sdk.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
scripts/lib/devtool/sdk.py +7 -4
@@ -95,8 +95,6 @@ def sdk_update(args, config, basepath, workspace): updateserver = args.updateserver if not updateserver: updateserver = config.get('SDK', 'updateserver', '') - if not updateserver: - raise DevtoolError("Update server not specified in config file, you must specify it on the command line") logger.debug("updateserver: %s" % updateserver) # Make sure we are using sdk-update from within SDK
@@ -297,9 +295,14 @@ def register_commands(subparsers, context): """Register devtool subcommands from the sdk plugin""" if context.fixed_setup: parser_sdk = subparsers.add_parser('sdk-update', - help='Update SDK components from a nominated location', + help='Update SDK components', + description='Updates installed SDK components from a remote server', group='sdk') - parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from', nargs='?') + updateserver = context.config.get('SDK', 'updateserver', '') + if updateserver: + parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from (default %s)' % updateserver, nargs='?') + else: + parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from') parser_sdk.add_argument('--skip-prepare', action="store_true", help='Skip re-preparing the build system after updating (for debugging only)') parser_sdk.set_defaults(func=sdk_update)
Signature · 1 line
2.5.0
PATCH 9/14 devtool: deploy-target: write deployed files list to target
In reply to Paul EggletonWhen running devtool deploy-target, we save a list of deployed files, and this list is used by devtool undeploy-target (or the next time deploy-target is run if the list is present, in case any files have been renamed or deleted since the first time). We were writing this file to the host, but it makes more sense to write the list to the target instead, so that if we for example swap in a different board, or switch hosts, things will work as expected.
In order to do this properly we have to construct a shell script and ship it over to the target so we can run it. The manifest is written out to a hidden directory in the root (/.devtool).
Fixes [YOCTO #7908].
--- scripts/lib/devtool/deploy.py | 144 ++++++++++++++++++++++++++++++------------ 1 file changed, 103 insertions(+), 41 deletions(-)
scripts/lib/devtool/deploy.py +103 -41
@@ -1,6 +1,6 @@ # Development tool - deploy/undeploy command plugin # -# Copyright (C) 2014-2015 Intel Corporation +# Copyright (C) 2014-2016 Intel Corporation # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as
@@ -19,10 +19,57 @@ import os import subprocess import logging +import tempfile +import shutil from devtool import exec_fakeroot, setup_tinfoil, check_workspace_recipe, DevtoolError logger = logging.getLogger('devtool') +deploylist_path = '/.devtool' + +def _prepare_remote_script(deploy, verbose=False): + """ + Prepare a shell script for running on the target to + deploy/undeploy files. We have to be careful what we put in this + script - only commands that are likely to be available on the + target are suitable (the target might be constrained, e.g. using + busybox rather than bash with coreutils). + """ + lines = [] + lines.append('#!/bin/sh') + lines.append('set -e') + lines.append('manifest="%s/$1.list"' % deploylist_path) + lines.append('if [ -f $manifest ] ; then') + # Read manifest in reverse and delete files / remove empty dirs + lines.append(' sed \'1!G;h;$!d\' $manifest | while read file') + lines.append(' do') + lines.append(' if [ -d $file ] ; then') + lines.append(' rmdir $file > /dev/null 2>&1 || true') + lines.append(' else') + lines.append(' rm $file') + lines.append(' fi') + lines.append(' done') + lines.append(' rm $manifest') + if not deploy: + # May as well remove all traces + lines.append(' rmdir `dirname $manifest` > /dev/null 2>&1 || true') + lines.append('fi') + + if deploy: + lines.append('mkdir -p `dirname $manifest`') + lines.append('mkdir -p $2') + if verbose: + lines.append(' tar xv -C $2 -f - | tee $manifest') + else: + lines.append(' tar xv -C $2 -f - > $manifest') + lines.append('sed -i "s!^./!$2!" $manifest') + # Delete the script itself + lines.append('rm $0') + lines.append('') + + return '\n'.join(lines) + + def deploy(args, config, basepath, workspace): """Entry point for the devtool 'deploy' subcommand""" import re
@@ -36,9 +83,8 @@ def deploy(args, config, basepath, workspace): destdir = '/' else: args.target = host - - deploy_dir = os.path.join(basepath, 'target_deploy', args.target) - deploy_file = os.path.join(deploy_dir, args.recipename + '.list') + if not destdir.endswith('/'): + destdir += '/' tinfoil = setup_tinfoil(basepath=basepath) try:
@@ -59,74 +105,90 @@ def deploy(args, config, basepath, workspace): print(' %s' % os.path.join(destdir, os.path.relpath(root, recipe_outdir), fn)) return 0 - if os.path.exists(deploy_file): - if undeploy(args, config, basepath, workspace): - # Error already shown - return 1 extraoptions = '' if args.no_host_check: extraoptions += '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' - if args.show_status: - tarextractopts = 'xv' - else: - tarextractopts = 'x' + if not args.show_status: extraoptions += ' -q' - # We cannot use scp here, because it doesn't preserve symlinks - ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'tar %s -C %s -f -\'' % (extraoptions, args.target, tarextractopts, destdir), cwd=recipe_outdir, shell=True) + + # In order to delete previously deployed files and have the manifest file on + # the target, we write out a shell script and then copy it to the target + # so we can then run it (piping tar output to it). + # (We cannot use scp here, because it doesn't preserve symlinks.) + tmpdir = tempfile.mkdtemp(prefix='devtool') + try: + tmpscript = '/tmp/devtool_deploy.sh' + shellscript = _prepare_remote_script(deploy=True, verbose=args.show_status) + # Write out the script to a file + with open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w') as f: + f.write(shellscript) + # Copy it to the target + ret = subprocess.call("scp %s %s/* %s:%s" % (extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True) + if ret != 0: + raise DevtoolError('Failed to copy script to %s - rerun with -s to ' + 'get a complete error message' % args.target) + finally: + shutil.rmtree(tmpdir) + + # Now run the script + ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir), cwd=recipe_outdir, shell=True) if ret != 0: raise DevtoolError('Deploy failed - rerun with -s to get a complete ' 'error message') logger.info('Successfully deployed %s' % recipe_outdir) - if not os.path.exists(deploy_dir): - os.makedirs(deploy_dir) - files_list = [] for root, _, files in os.walk(recipe_outdir): for filename in files: filename = os.path.relpath(os.path.join(root, filename), recipe_outdir) files_list.append(os.path.join(destdir, filename)) - with open(deploy_file, 'w') as fobj: - fobj.write('\n'.join(files_list)) - return 0 def undeploy(args, config, basepath, workspace): """Entry point for the devtool 'undeploy' subcommand""" - deploy_file = os.path.join(basepath, 'target_deploy', args.target, args.recipename + '.list') - if not os.path.exists(deploy_file): - raise DevtoolError('%s has not been deployed' % args.recipename) - - if args.dry_run: - print('Previously deployed files to be un-deployed for %s on target %s:' % (args.recipename, args.target)) - with open(deploy_file, 'r') as f: - for line in f: - print(' %s' % line.rstrip()) - return 0 - extraoptions = '' if args.no_host_check: extraoptions += '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' if not args.show_status: extraoptions += ' -q' - ret = subprocess.call("scp %s %s %s:/tmp" % (extraoptions, deploy_file, args.target), shell=True) - if ret != 0: - raise DevtoolError('Failed to copy file list to %s - rerun with -s to ' - 'get a complete error message' % args.target) + args.target = args.target.split(':')[0] - ret = subprocess.call("ssh %s %s 'xargs -n1 rm -f </tmp/%s'" % (extraoptions, args.target, os.path.basename(deploy_file)), shell=True) - if ret == 0: - logger.info('Successfully undeployed %s' % args.recipename) - os.remove(deploy_file) - else: + if args.dry_run: + listfile = os.path.join(deploylist_path, '%s.list' % args.recipename) + print('Previously deployed files to be un-deployed for %s on target %s:' % (args.recipename, args.target)) + ret = subprocess.call('ssh %s %s \'[ -f %s ] && cat %s || true\'' % (extraoptions, args.target, listfile, listfile), shell=True) + if ret != 0: + raise DevtoolError('Undeploy failed - rerun with -s to get a complete ' + 'error message') + return 0 + + tmpdir = tempfile.mkdtemp(prefix='devtool') + try: + tmpscript = '/tmp/devtool_undeploy.sh' + shellscript = _prepare_remote_script(deploy=False) + # Write out the script to a file + with open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w') as f: + f.write(shellscript) + # Copy it to the target + ret = subprocess.call("scp %s %s/* %s:%s" % (extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True) + if ret != 0: + raise DevtoolError('Failed to copy script to %s - rerun with -s to ' + 'get a complete error message' % args.target) + finally: + shutil.rmtree(tmpdir) + + # Now run the script + ret = subprocess.call('ssh %s %s \'sh %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename), shell=True) + if ret != 0: raise DevtoolError('Undeploy failed - rerun with -s to get a complete ' 'error message') - return ret + logger.info('Successfully undeployed %s' % args.recipename) + return 0 def register_commands(subparsers, context):
Signature · 1 line
2.5.0
PATCH 10/14 devtool: undeploy-target: support undeploying all recipes
In reply to Paul EggletonIf you want to put the target device back to exactly how it was before devtool deploy-target started poking things into it, then it would make things easier if you didn't have to figure out which recipes were deployed. Now that we have the list stored on the target we can determine this reliably, so add a -a/--all option to undeploy-target to undeploy everything that has been deployed.
One of the side-effects of this is that the dry-run functionality for undeploy-target had to be reimplemented to actually run the script on the target, since we have no way of knowing what's been deployed from the host side. We don't need to do the same for deploy-target though since we know exactly which files will be deployed without referring to the target.
--- scripts/lib/devtool/deploy.py | 59 ++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 20 deletions(-)
scripts/lib/devtool/deploy.py +39 -20
@@ -21,13 +21,14 @@ import subprocess import logging import tempfile import shutil +import argparse_oe from devtool import exec_fakeroot, setup_tinfoil, check_workspace_recipe, DevtoolError logger = logging.getLogger('devtool') deploylist_path = '/.devtool' -def _prepare_remote_script(deploy, verbose=False): +def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=False): """ Prepare a shell script for running on the target to deploy/undeploy files. We have to be careful what we put in this
@@ -38,19 +39,33 @@ def _prepare_remote_script(deploy, verbose=False): lines = [] lines.append('#!/bin/sh') lines.append('set -e') + if undeployall: + # Yes, I know this is crude - but it does work + lines.append('for entry in %s/*.list; do' % deploylist_path) + lines.append('[ ! -f $entry ] && exit') + lines.append('set `basename $entry | sed "s/.list//"`') + if dryrun: + if not deploy: + lines.append('echo "Previously deployed files for $1:"') lines.append('manifest="%s/$1.list"' % deploylist_path) lines.append('if [ -f $manifest ] ; then') # Read manifest in reverse and delete files / remove empty dirs lines.append(' sed \'1!G;h;$!d\' $manifest | while read file') lines.append(' do') - lines.append(' if [ -d $file ] ; then') - lines.append(' rmdir $file > /dev/null 2>&1 || true') - lines.append(' else') - lines.append(' rm $file') - lines.append(' fi') + if dryrun: + lines.append(' if [ ! -d $file ] ; then') + lines.append(' echo $file') + lines.append(' fi') + else: + lines.append(' if [ -d $file ] ; then') + lines.append(' rmdir $file > /dev/null 2>&1 || true') + lines.append(' else') + lines.append(' rm $file') + lines.append(' fi') lines.append(' done') - lines.append(' rm $manifest') - if not deploy: + if not dryrun: + lines.append(' rm $manifest') + if not deploy and not dryrun: # May as well remove all traces lines.append(' rmdir `dirname $manifest` > /dev/null 2>&1 || true') lines.append('fi')
@@ -63,6 +78,12 @@ def _prepare_remote_script(deploy, verbose=False): else: lines.append(' tar xv -C $2 -f - > $manifest') lines.append('sed -i "s!^./!$2!" $manifest') + + if undeployall: + if not dryrun: + lines.append('echo "NOTE: Successfully undeployed $1"') + lines.append('done') + # Delete the script itself lines.append('rm $0') lines.append('')
@@ -149,6 +170,11 @@ def deploy(args, config, basepath, workspace): def undeploy(args, config, basepath, workspace): """Entry point for the devtool 'undeploy' subcommand""" + if args.all and args.recipename: + raise argparse_oe.ArgumentUsageError('Cannot specify -a/--all with a recipe name', 'undeploy-target') + elif not args.recipename and not args.all: + raise argparse_oe.ArgumentUsageError('If you don\'t specify a recipe, you must specify -a/--all', 'undeploy-target') + extraoptions = '' if args.no_host_check: extraoptions += '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
@@ -157,19 +183,10 @@ def undeploy(args, config, basepath, workspace): args.target = args.target.split(':')[0] - if args.dry_run: - listfile = os.path.join(deploylist_path, '%s.list' % args.recipename) - print('Previously deployed files to be un-deployed for %s on target %s:' % (args.recipename, args.target)) - ret = subprocess.call('ssh %s %s \'[ -f %s ] && cat %s || true\'' % (extraoptions, args.target, listfile, listfile), shell=True) - if ret != 0: - raise DevtoolError('Undeploy failed - rerun with -s to get a complete ' - 'error message') - return 0 - tmpdir = tempfile.mkdtemp(prefix='devtool') try: tmpscript = '/tmp/devtool_undeploy.sh' - shellscript = _prepare_remote_script(deploy=False) + shellscript = _prepare_remote_script(deploy=False, dryrun=args.dry_run, undeployall=args.all) # Write out the script to a file with open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w') as f: f.write(shellscript)
@@ -187,7 +204,8 @@ def undeploy(args, config, basepath, workspace): raise DevtoolError('Undeploy failed - rerun with -s to get a complete ' 'error message') - logger.info('Successfully undeployed %s' % args.recipename) + if not args.all and not args.dry_run: + logger.info('Successfully undeployed %s' % args.recipename) return 0
@@ -208,9 +226,10 @@ def register_commands(subparsers, context): help='Undeploy recipe output files in live target machine', description='Un-deploys recipe output files previously deployed to a live target machine by devtool deploy-target.', group='testbuild') - parser_undeploy.add_argument('recipename', help='Recipe to undeploy') + parser_undeploy.add_argument('recipename', help='Recipe to undeploy (if not using -a/--all)', nargs='?') parser_undeploy.add_argument('target', help='Live target machine running an ssh server: user@hostname') parser_undeploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true') parser_undeploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true') + parser_undeploy.add_argument('-a', '--all', help='Undeploy all recipes deployed on the target', action='store_true') parser_undeploy.add_argument('-n', '--dry-run', help='List files to be undeployed only', action='store_true') parser_undeploy.set_defaults(func=undeploy)
Signature · 1 line
2.5.0
PATCH 11/14 devtool: deploy-target: preserve existing files
In reply to Paul EggletonIf files would be overwritten by the deployment, preserve them in a separate location on the target so that they can be restored if you later run devtool undeploy-target.
At the same time, also check for sufficient space before starting the operation so that we avoid potentially failing part way through.
Fixes [YOCTO #8978].
--- scripts/lib/devtool/deploy.py | 87 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-)
scripts/lib/devtool/deploy.py +78 -9
@@ -28,7 +28,7 @@ logger = logging.getLogger('devtool') deploylist_path = '/.devtool' -def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=False): +def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=False, nopreserve=False, nocheckspace=False): """ Prepare a shell script for running on the target to deploy/undeploy files. We have to be careful what we put in this
@@ -48,6 +48,7 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals if not deploy: lines.append('echo "Previously deployed files for $1:"') lines.append('manifest="%s/$1.list"' % deploylist_path) + lines.append('preservedir="%s/$1.preserve"' % deploylist_path) lines.append('if [ -f $manifest ] ; then') # Read manifest in reverse and delete files / remove empty dirs lines.append(' sed \'1!G;h;$!d\' $manifest | while read file')
@@ -58,7 +59,10 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals lines.append(' fi') else: lines.append(' if [ -d $file ] ; then') - lines.append(' rmdir $file > /dev/null 2>&1 || true') + # Avoid deleting a preserved directory in case it has special perms + lines.append(' if [ ! -d $preservedir/$file ] ; then') + lines.append(' rmdir $file > /dev/null 2>&1 || true') + lines.append(' fi') lines.append(' else') lines.append(' rm $file') lines.append(' fi')
@@ -71,6 +75,39 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals lines.append('fi') if deploy: + if not nocheckspace: + # Check for available space + # FIXME This doesn't take into account files spread across multiple + # partitions, but doing that is non-trivial + # Find the part of the destination path that exists + lines.append('checkpath="$2"') + lines.append('while [ "$checkpath" != "/" ] && [ ! -e $checkpath ]') + lines.append('do') + lines.append(' checkpath=`dirname "$checkpath"`') + lines.append('done') + lines.append('freespace=`df -P $checkpath | sed "1d" | awk \'{ print $4 }\'`') + # First line of the file is the total space + lines.append('total=`head -n1 $3`') + lines.append('if [ $total -gt $freespace ] ; then') + lines.append(' echo "ERROR: insufficient space on target (available ${freespace}, needed ${total})"') + lines.append(' exit 1') + lines.append('fi') + if not nopreserve: + # Preserve any files that exist. Note that this will add to the + # preserved list with successive deployments if the list of files + # deployed changes, but because we've deleted any previously + # deployed files at this point it will never preserve anything + # that was deployed, only files that existed prior to any deploying + # (which makes the most sense) + lines.append('cat $3 | sed "1d" | while read file fsize') + lines.append('do') + lines.append(' if [ -e $file ] ; then') + lines.append(' dest="$preservedir/$file"') + lines.append(' mkdir -p `dirname $dest`') + lines.append(' mv $file $dest') + lines.append(' fi') + lines.append('done') + lines.append('rm $3') lines.append('mkdir -p `dirname $manifest`') lines.append('mkdir -p $2') if verbose:
@@ -78,6 +115,14 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals else: lines.append(' tar xv -C $2 -f - > $manifest') lines.append('sed -i "s!^./!$2!" $manifest') + elif not dryrun: + # Put any preserved files back + lines.append('if [ -d $preservedir ] ; then') + lines.append(' cd $preservedir') + lines.append(' find . -type f -exec mv {} /{} \;') + lines.append(' cd /') + lines.append(' rm -rf $preservedir') + lines.append('fi') if undeployall: if not dryrun:
@@ -94,6 +139,7 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals def deploy(args, config, basepath, workspace): """Entry point for the devtool 'deploy' subcommand""" import re + import math import oe.recipeutils check_workspace_recipe(workspace, args.recipename, checksrc=False)
@@ -119,11 +165,23 @@ def deploy(args, config, basepath, workspace): 'recipe? If so, the install step has not installed ' 'any files.' % args.recipename) + filelist = [] + ftotalsize = 0 + for root, _, files in os.walk(recipe_outdir): + for fn in files: + # Get the size in kiB (since we'll be comparing it to the output of du -k) + # MUST use lstat() here not stat() or getfilesize() since we don't want to + # dereference symlinks + fsize = int(math.ceil(float(os.lstat(os.path.join(root, fn)).st_size)/1024)) + ftotalsize += fsize + # The path as it would appear on the target + fpath = os.path.join(destdir, os.path.relpath(root, recipe_outdir), fn) + filelist.append((fpath, fsize)) + if args.dry_run: print('Files to be deployed for %s on target %s:' % (args.recipename, args.target)) - for root, _, files in os.walk(recipe_outdir): - for fn in files: - print(' %s' % os.path.join(destdir, os.path.relpath(root, recipe_outdir), fn)) + for item, _ in filelist: + print(' %s' % item) return 0
@@ -140,11 +198,20 @@ def deploy(args, config, basepath, workspace): tmpdir = tempfile.mkdtemp(prefix='devtool') try: tmpscript = '/tmp/devtool_deploy.sh' - shellscript = _prepare_remote_script(deploy=True, verbose=args.show_status) + tmpfilelist = os.path.join(os.path.dirname(tmpscript), 'devtool_deploy.list') + shellscript = _prepare_remote_script(deploy=True, + verbose=args.show_status, + nopreserve=args.no_preserve, + nocheckspace=args.no_check_space) # Write out the script to a file with open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w') as f: f.write(shellscript) - # Copy it to the target + # Write out the file list + with open(os.path.join(tmpdir, os.path.basename(tmpfilelist)), 'w') as f: + f.write('%d\n' % ftotalsize) + for fpath, fsize in filelist: + f.write('%s %d\n' % (fpath, fsize)) + # Copy them to the target ret = subprocess.call("scp %s %s/* %s:%s" % (extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True) if ret != 0: raise DevtoolError('Failed to copy script to %s - rerun with -s to '
@@ -153,7 +220,7 @@ def deploy(args, config, basepath, workspace): shutil.rmtree(tmpdir) # Now run the script - ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir), cwd=recipe_outdir, shell=True) + ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir, tmpfilelist), cwd=recipe_outdir, shell=True) if ret != 0: raise DevtoolError('Deploy failed - rerun with -s to get a complete ' 'error message')
@@ -213,13 +280,15 @@ def register_commands(subparsers, context): """Register devtool subcommands from the deploy plugin""" parser_deploy = subparsers.add_parser('deploy-target', help='Deploy recipe output files to live target machine', - description='Deploys a recipe\'s build output (i.e. the output of the do_install task) to a live target machine over ssh. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', + description='Deploys a recipe\'s build output (i.e. the output of the do_install task) to a live target machine over ssh. By default, any existing files will be preserved instead of being overwritten and will be restored if you run devtool undeploy-target. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', group='testbuild') parser_deploy.add_argument('recipename', help='Recipe to deploy') parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]') parser_deploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true') parser_deploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true') parser_deploy.add_argument('-n', '--dry-run', help='List files to be deployed only', action='store_true') + parser_deploy.add_argument('-p', '--no-preserve', help='Do not preserve existing files', action='store_true') + parser_deploy.add_argument('--no-check-space', help='Do not check for available space before deploying', action='store_true') parser_deploy.set_defaults(func=deploy) parser_undeploy = subparsers.add_parser('undeploy-target',
Signature · 1 line
2.5.0
PATCH 12/14 devtool: modify: tweak help description for behaviour change
In reply to Paul EggletonI should have adjusted this in OE-Core commit 80a44e52609a89d9ffe816181ae193af491c06ac where the behaviour changed.
--- scripts/lib/devtool/standard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
scripts/lib/devtool/standard.py +2 -2
@@ -1320,7 +1320,7 @@ def register_commands(subparsers, context): parser_add.set_defaults(func=add) parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe', - description='Enables modifying the source for an existing recipe. You can either provide your own pre-prepared source tree, or specify -x/--extract to extract the source being fetched by the recipe.', + description='Sets up the build environment to modify the source for an existing recipe. The default behaviour is to extract the source being fetched by the recipe into a git tree so you can work on it; alternatively if you already have your own pre-prepared source tree you can specify -n/--no-extract.', group='starting', order=90) parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)') parser_modify.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
@@ -1331,7 +1331,7 @@ def register_commands(subparsers, context): group = parser_modify.add_mutually_exclusive_group() group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true") group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true") - parser_modify.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (only when using -x) (default "%(default)s")') + parser_modify.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (when not using -n/--no-extract) (default "%(default)s")') parser_modify.set_defaults(func=modify) parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
Signature · 1 line
2.5.0
PATCH 13/14 recipetool: create: add additional extension mechanisms
In reply to Paul EggletonAdd a means of extending the dependency extraction for autotools and cmake.
Note: in order to have this work, you need to have an __init__.py in the lib/recipetool directory within your layer along with the module implementing the handlers, and the __init__.py needs to contain:
# Enable other layers to have modules in the same named directory from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
--- scripts/lib/recipetool/create_buildsys.py | 119 +++++++++++++++++++++-- scripts/lib/recipetool/create_buildsys_python.py | 2 +- 2 files changed, 113 insertions(+), 8 deletions(-)
scripts/lib/recipetool/create_buildsys.py +112 -7
@@ -22,6 +22,12 @@ from recipetool.create import RecipeHandler, validate_pv logger = logging.getLogger('recipetool') tinfoil = None +plugins = None + +def plugin_init(pluginlist): + # Take a reference to the list so we can use it later + global plugins + plugins = pluginlist def tinfoil_init(instance): global tinfoil
@@ -48,6 +54,13 @@ class CmakeRecipeHandler(RecipeHandler): @staticmethod def extract_cmake_deps(outlines, srctree, extravalues, cmakelistsfile=None): + # Find all plugins that want to register handlers + logger.debug('Loading cmake handlers') + handlers = [] + for plugin in plugins: + if hasattr(plugin, 'register_cmake_handlers'): + plugin.register_cmake_handlers(handlers) + values = {} inherits = []
@@ -152,6 +165,9 @@ class CmakeRecipeHandler(RecipeHandler): with open(fn, 'r') as f: for line in f: line = line.strip() + for handler in handlers: + if handler.process_line(srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values): + continue res = include_re.match(line) if res: includefn = bb.utils.which(':'.join(searchpaths), res.group(1))
@@ -194,7 +210,15 @@ class CmakeRecipeHandler(RecipeHandler): if res: origpkg = res.group(1) pkg = interpret_value(origpkg.lower()) - if pkg == 'gettext': + found = False + for handler in handlers: + if handler.process_findpackage(srctree, fn, pkg, deps, outlines, inherits, values): + logger.debug('Mapped CMake package %s via handler %s' % (pkg, handler.__class__.__name__)) + found = True + break + if found: + continue + elif pkg == 'gettext': inherits.append('gettext') elif pkg == 'perl': inherits.append('perlnative')
@@ -207,6 +231,7 @@ class CmakeRecipeHandler(RecipeHandler): else: dep = cmake_pkgmap.get(pkg, None) if dep: + logger.debug('Mapped CMake package %s to recipe %s via internal list' % (pkg, dep)) deps.append(dep) elif dep is None: unmappedpkgs.append(origpkg)
@@ -236,11 +261,39 @@ class CmakeRecipeHandler(RecipeHandler): RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data) + for handler in handlers: + handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values) + if inherits: values['inherit'] = ' '.join(list(set(inherits))) return values + +class CmakeExtensionHandler(object): + '''Base class for CMake extension handlers''' + def process_line(self, srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values): + ''' + Handle a line parsed out of an CMake file. + Return True if you've completely handled the passed in line, otherwise return False. + ''' + return False + + def process_findpackage(self, srctree, fn, pkg, deps, outlines, inherits, values): + ''' + Handle a find_package package parsed out of a CMake file. + Return True if you've completely handled the passed in package, otherwise return False. + ''' + return False + + def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values): + ''' + Apply any desired post-processing on the output + ''' + return + + + class SconsRecipeHandler(RecipeHandler): def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): if 'buildsystem' in handled:
@@ -255,6 +308,7 @@ class SconsRecipeHandler(RecipeHandler): return True return False + class QmakeRecipeHandler(RecipeHandler): def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): if 'buildsystem' in handled:
@@ -266,6 +320,7 @@ class QmakeRecipeHandler(RecipeHandler): return True return False + class AutotoolsRecipeHandler(RecipeHandler): def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): if 'buildsystem' in handled:
@@ -322,6 +377,13 @@ class AutotoolsRecipeHandler(RecipeHandler): def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None): import shlex + # Find all plugins that want to register handlers + logger.debug('Loading autotools handlers') + handlers = [] + for plugin in plugins: + if hasattr(plugin, 'register_autotools_handlers'): + plugin.register_autotools_handlers(handlers) + values = {} inherits = []
@@ -384,6 +446,9 @@ class AutotoolsRecipeHandler(RecipeHandler): unmapped = [] def process_macro(keyword, value): + for handler in handlers: + if handler.process_macro(srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values): + return if keyword == 'PKG_CHECK_MODULES': res = pkg_re.search(value) if res:
@@ -409,6 +474,9 @@ class AutotoolsRecipeHandler(RecipeHandler): if res: for prog in shlex.split(res.group(1)): prog = prog.split()[0] + for handler in handlers: + if handler.process_prog(srctree, keyword, value, prog, deps, outlines, inherits, values): + return progclass = progclassmap.get(prog, None) if progclass: inherits.append(progclass)
@@ -537,6 +605,10 @@ class AutotoolsRecipeHandler(RecipeHandler): 'AM_INIT_AUTOMAKE', 'define(', ] + + for handler in handlers: + handler.extend_keywords(keywords) + for srcfile in srcfiles: nesting = 0 in_keyword = ''
@@ -581,12 +653,44 @@ class AutotoolsRecipeHandler(RecipeHandler): RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data) + for handler in handlers: + handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values) + if inherits: values['inherit'] = ' '.join(list(set(inherits))) return values +class AutotoolsExtensionHandler(object): + '''Base class for Autotools extension handlers''' + def process_macro(self, srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values): + ''' + Handle a macro parsed out of an autotools file. Note that if you want this to be called + for any macro other than the ones AutotoolsRecipeHandler already looks for, you'll need + to add it to the keywords list in extend_keywords(). + Return True if you've completely handled the passed in macro, otherwise return False. + ''' + return False + + def extend_keywords(self, keywords): + '''Adds keywords to be recognised by the parser (so that you get a call to process_macro)''' + return + + def process_prog(self, srctree, keyword, value, prog, deps, outlines, inherits, values): + ''' + Handle an AC_PATH_PROG, AC_CHECK_PROG etc. line + Return True if you've completely handled the passed in macro, otherwise return False. + ''' + return False + + def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values): + ''' + Apply any desired post-processing on the output + ''' + return + + class MakefileRecipeHandler(RecipeHandler): def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): if 'buildsystem' in handled:
@@ -703,11 +807,12 @@ class SpecFileRecipeHandler(RecipeHandler): break def register_recipe_handlers(handlers): - # These are in a specific order so that the right one is detected first - handlers.append(CmakeRecipeHandler()) - handlers.append(AutotoolsRecipeHandler()) - handlers.append(SconsRecipeHandler()) - handlers.append(QmakeRecipeHandler()) - handlers.append(MakefileRecipeHandler()) + # Set priorities with some gaps so that other plugins can insert + # their own handlers (so avoid changing these numbers) + handlers.append((CmakeRecipeHandler(), 50)) + handlers.append((AutotoolsRecipeHandler(), 40)) + handlers.append((SconsRecipeHandler(), 30)) + handlers.append((QmakeRecipeHandler(), 20)) + handlers.append((MakefileRecipeHandler(), 10)) handlers.append((VersionFileRecipeHandler(), -1)) handlers.append((SpecFileRecipeHandler(), -1))
scripts/lib/recipetool/create_buildsys_python.py +1 -1
@@ -716,4 +716,4 @@ def has_non_literals(value): def register_recipe_handlers(handlers): # We need to make sure this is ahead of the makefile fallback handler - handlers.insert(0, PythonRecipeHandler()) + handlers.append((PythonRecipeHandler(), 70))
Signature · 1 line
2.5.0
PATCH 14/14 recipetool: create: improve CMake package mapping
In reply to Paul Eggleton* Package names are actually case sensitive near as I can tell, so we shouldn't be lowercasing them everywhere.
* Look for CMake packages in pkgdata and map those back to recipes, so we aren't dependent on the hardcoded mappings (though those are still preserved).
* Avoid duplicates in the unmapped package list
--- scripts/lib/recipetool/create.py | 15 +++++++++--- scripts/lib/recipetool/create_buildsys.py | 39 ++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 14 deletions(-)
scripts/lib/recipetool/create.py +12 -3
@@ -43,6 +43,7 @@ def tinfoil_init(instance): class RecipeHandler(object): recipelibmap = {} recipeheadermap = {} + recipecmakefilemap = {} @staticmethod def load_libmap(d):
@@ -90,15 +91,18 @@ class RecipeHandler(object): RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2' @staticmethod - def load_headermap(d): - '''Build up lib headerfile->recipe mapping''' + def load_devel_filemap(d): + '''Build up development file->recipe mapping''' if RecipeHandler.recipeheadermap: return + pkgdata_dir = d.getVar('PKGDATA_DIR', True) includedir = d.getVar('includedir', True) + cmakedir = os.path.join(d.getVar('libdir', True), 'cmake') for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')): with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f: pn = None headers = [] + cmakefiles = [] for line in f: if line.startswith('PN:'): pn = line.split(':', 1)[-1].strip()
@@ -108,9 +112,14 @@ class RecipeHandler(object): for fullpth in sorted(dictval): if fullpth.startswith(includedir) and fullpth.endswith('.h'): headers.append(os.path.relpath(fullpth, includedir)) + elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'): + cmakefiles.append(os.path.relpath(fullpth, cmakedir)) if pn and headers: for header in headers: RecipeHandler.recipeheadermap[header] = pn + if pn and cmakefiles: + for fn in cmakefiles: + RecipeHandler.recipecmakefilemap[fn] = pn @staticmethod def checkfiles(path, speclist, recursive=False):
@@ -172,7 +181,7 @@ class RecipeHandler(object): deps.append(recipe) elif recipe is None: if header: - RecipeHandler.load_headermap(d) + RecipeHandler.load_devel_filemap(d) recipe = RecipeHandler.recipeheadermap.get(header, None) if recipe: deps.append(recipe)
scripts/lib/recipetool/create_buildsys.py +28 -11
@@ -17,6 +17,7 @@ import re import logging +import glob from recipetool.create import RecipeHandler, validate_pv logger = logging.getLogger('recipetool')
@@ -156,6 +157,16 @@ class CmakeRecipeHandler(RecipeHandler): subdir_re = re.compile('add_subdirectory\s*\(\s*([^)\s]*)\s*([^)\s]*)\s*\)', re.IGNORECASE) dep_re = re.compile('([^ ><=]+)( *[<>=]+ *[^ ><=]+)?') + def find_cmake_package(pkg): + RecipeHandler.load_devel_filemap(tinfoil.config_data) + for fn, pn in RecipeHandler.recipecmakefilemap.iteritems(): + splitname = fn.split('/') + if len(splitname) > 1: + if splitname[0].lower().startswith(pkg.lower()): + if splitname[1] == '%s-config.cmake' % pkg.lower() or splitname[1] == '%sConfig.cmake' % pkg or splitname[1] == 'Find%s.cmake' % pkg: + return pn + return None + def interpret_value(value): return value.strip('"')
@@ -209,7 +220,7 @@ class CmakeRecipeHandler(RecipeHandler): res = findpackage_re.match(line) if res: origpkg = res.group(1) - pkg = interpret_value(origpkg.lower()) + pkg = interpret_value(origpkg) found = False for handler in handlers: if handler.process_findpackage(srctree, fn, pkg, deps, outlines, inherits, values):
@@ -218,23 +229,29 @@ class CmakeRecipeHandler(RecipeHandler): break if found: continue - elif pkg == 'gettext': + elif pkg == 'Gettext': inherits.append('gettext') - elif pkg == 'perl': + elif pkg == 'Perl': inherits.append('perlnative') - elif pkg == 'pkgconfig': + elif pkg == 'PkgConfig': inherits.append('pkgconfig') - elif pkg == 'pythoninterp': + elif pkg == 'PythonInterp': inherits.append('pythonnative') - elif pkg == 'pythonlibs': + elif pkg == 'PythonLibs': inherits.append('python-dir') else: - dep = cmake_pkgmap.get(pkg, None) + # Try to map via looking at installed CMake packages in pkgdata + dep = find_cmake_package(pkg) if dep: - logger.debug('Mapped CMake package %s to recipe %s via internal list' % (pkg, dep)) + logger.debug('Mapped CMake package %s to recipe %s via pkgdata' % (pkg, dep)) deps.append(dep) - elif dep is None: - unmappedpkgs.append(origpkg) + else: + dep = cmake_pkgmap.get(pkg.lower(), None) + if dep: + logger.debug('Mapped CMake package %s to recipe %s via internal list' % (pkg, dep)) + deps.append(dep) + elif dep is None: + unmappedpkgs.append(origpkg) continue res = checklib_re.match(line) if res:
@@ -257,7 +274,7 @@ class CmakeRecipeHandler(RecipeHandler): parse_cmake_file(srcfiles[0]) if unmappedpkgs: - outlines.append('# NOTE: unable to map the following CMake package dependencies: %s' % ' '.join(unmappedpkgs)) + outlines.append('# NOTE: unable to map the following CMake package dependencies: %s' % ' '.join(list(set(unmappedpkgs)))) RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)
Signature · 1 line
2.5.0
Re: [PATCH 01/14] devtool: minor fix for error message
In reply to Paul EggletonQuoted 13 lines · Paul Eggleton
There is no -N/--name option for devtool, that's a recipetool option - with devtool you just specify the name as a positional argument.
Signed-off-by Paul Eggleton <[email protected]>--- scripts/lib/devtool/standard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)scripts/lib/devtool/standard.py +0
-0
scripts/lib/devtool/standard.py def add(args, config, basepath, workspace): · line 153
Quoted 5 lines
scripts/lib/devtool/standard.py +1
-1@@ -150,7 +150,7 @@ def add(args, config, basepath, workspace): stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create -o %s "%s" %s' % (color, tempdir, source, extracmdopts)) except bb.process.ExecutionError as e: if e.exitcode == 15: - raise DevtoolError('Unable to auto-determine name from source tree, please specify it with -N/--name') + raise DevtoolError('Unable to auto-determine recipe name from source tree, please specify it on the command line')
may be reword it a bit to "Could not auto detect recipe name, please specify on commandline"
Quoted 11 lines
scripts/lib/devtool/standard.py +0
-1else: raise DevtoolError('Command \'%s\' failed:\n%s' % (e.command, e.stdout)) --2.5.0
--
_______________________________________________
Openembedded-core mailing list
[email protected]
http://lists.openembedded.org/mailman/listinfo/openembedded-core
Re: [PATCH 11/14] devtool: deploy-target: preserve existing files
In reply to Paul EggletonQuoted 173 lines · Paul Eggleton
If files would be overwritten by the deployment, preserve them in a separate location on the target so that they can be restored if you later run devtool undeploy-target.
At the same time, also check for sufficient space before starting the operation so that we avoid potentially failing part way through.
Fixes [YOCTO #8978].
Signed-off-by Paul Eggleton <[email protected]>--- scripts/lib/devtool/deploy.py | 87 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-)scripts/lib/devtool/deploy.py +75
-8@@ -28,7 +28,7 @@ logger = logging.getLogger('devtool') deploylist_path = '/.devtool' -def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=False): +def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=False, nopreserve=False, nocheckspace=False): """ Prepare a shell script for running on the target to deploy/undeploy files. We have to be careful what we put in this@@ -48,6 +48,7 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals if not deploy: lines.append('echo "Previously deployed files for $1:"') lines.append('manifest="%s/$1.list"' % deploylist_path) + lines.append('preservedir="%s/$1.preserve"' % deploylist_path) lines.append('if [ -f $manifest ] ; then') # Read manifest in reverse and delete files / remove empty dirs lines.append(' sed \'1!G;h;$!d\' $manifest | while read file')@@ -58,7 +59,10 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals lines.append(' fi') else: lines.append(' if [ -d $file ] ; then') - lines.append(' rmdir $file > /dev/null 2>&1 || true') + # Avoid deleting a preserved directory in case it has special perms + lines.append(' if [ ! -d $preservedir/$file ] ; then') + lines.append(' rmdir $file > /dev/null 2>&1 || true') + lines.append(' fi') lines.append(' else') lines.append(' rm $file') lines.append(' fi')@@ -71,6 +75,39 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals lines.append('fi') if deploy: + if not nocheckspace: + # Check for available space + # FIXME This doesn't take into account files spread across multiple + # partitions, but doing that is non-trivial + # Find the part of the destination path that exists + lines.append('checkpath="$2"') + lines.append('while [ "$checkpath" != "/" ] && [ ! -e $checkpath ]') + lines.append('do') + lines.append(' checkpath=`dirname "$checkpath"`') + lines.append('done') + lines.append('freespace=`df -P $checkpath | sed "1d" | awk \'{ print $4 }\'`') + # First line of the file is the total space + lines.append('total=`head -n1 $3`') + lines.append('if [ $total -gt $freespace ] ; then') + lines.append(' echo "ERROR: insufficient space on target (available ${freespace}, needed ${total})"') + lines.append(' exit 1') + lines.append('fi') + if not nopreserve: + # Preserve any files that exist. Note that this will add to the + # preserved list with successive deployments if the list of files + # deployed changes, but because we've deleted any previously + # deployed files at this point it will never preserve anything + # that was deployed, only files that existed prior to any deploying + # (which makes the most sense) + lines.append('cat $3 | sed "1d" | while read file fsize') + lines.append('do') + lines.append(' if [ -e $file ] ; then') + lines.append(' dest="$preservedir/$file"') + lines.append(' mkdir -p `dirname $dest`') + lines.append(' mv $file $dest') + lines.append(' fi') + lines.append('done') + lines.append('rm $3') lines.append('mkdir -p `dirname $manifest`') lines.append('mkdir -p $2') if verbose:@@ -78,6 +115,14 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals else: lines.append(' tar xv -C $2 -f - > $manifest') lines.append('sed -i "s!^./!$2!" $manifest') + elif not dryrun: + # Put any preserved files back + lines.append('if [ -d $preservedir ] ; then') + lines.append(' cd $preservedir') + lines.append(' find . -type f -exec mv {} /{} \;') + lines.append(' cd /') + lines.append(' rm -rf $preservedir') + lines.append('fi') if undeployall: if not dryrun:@@ -94,6 +139,7 @@ def _prepare_remote_script(deploy, verbose=False, dryrun=False, undeployall=Fals def deploy(args, config, basepath, workspace): """Entry point for the devtool 'deploy' subcommand""" import re + import math import oe.recipeutils check_workspace_recipe(workspace, args.recipename, checksrc=False)@@ -119,11 +165,23 @@ def deploy(args, config, basepath, workspace): 'recipe? If so, the install step has not installed ' 'any files.' % args.recipename) + filelist = [] + ftotalsize = 0 + for root, _, files in os.walk(recipe_outdir): + for fn in files: + # Get the size in kiB (since we'll be comparing it to the output of du -k) + # MUST use lstat() here not stat() or getfilesize() since we don't want to + # dereference symlinks + fsize = int(math.ceil(float(os.lstat(os.path.join(root, fn)).st_size)/1024)) + ftotalsize += fsize + # The path as it would appear on the target + fpath = os.path.join(destdir, os.path.relpath(root, recipe_outdir), fn) + filelist.append((fpath, fsize)) + if args.dry_run: print('Files to be deployed for %s on target %s:' % (args.recipename, args.target)) - for root, _, files in os.walk(recipe_outdir): - for fn in files: - print(' %s' % os.path.join(destdir, os.path.relpath(root, recipe_outdir), fn)) + for item, _ in filelist: + print(' %s' % item) return 0@@ -140,11 +198,20 @@ def deploy(args, config, basepath, workspace): tmpdir = tempfile.mkdtemp(prefix='devtool') try: tmpscript = '/tmp/devtool_deploy.sh' - shellscript = _prepare_remote_script(deploy=True, verbose=args.show_status) + tmpfilelist = os.path.join(os.path.dirname(tmpscript), 'devtool_deploy.list') + shellscript = _prepare_remote_script(deploy=True, + verbose=args.show_status, + nopreserve=args.no_preserve, + nocheckspace=args.no_check_space) # Write out the script to a file with open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w') as f: f.write(shellscript) - # Copy it to the target + # Write out the file list + with open(os.path.join(tmpdir, os.path.basename(tmpfilelist)), 'w') as f: + f.write('%d\n' % ftotalsize) + for fpath, fsize in filelist: + f.write('%s %d\n' % (fpath, fsize)) + # Copy them to the target ret = subprocess.call("scp %s %s/* %s:%s" % (extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True) if ret != 0: raise DevtoolError('Failed to copy script to %s - rerun with -s to '@@ -153,7 +220,7 @@ def deploy(args, config, basepath, workspace): shutil.rmtree(tmpdir) # Now run the script - ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir), cwd=recipe_outdir, shell=True) + ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir, tmpfilelist), cwd=recipe_outdir, shell=True) if ret != 0: raise DevtoolError('Deploy failed - rerun with -s to get a complete ' 'error message')@@ -213,13 +280,15 @@ def register_commands(subparsers, context): """Register devtool subcommands from the deploy plugin""" parser_deploy = subparsers.add_parser('deploy-target', help='Deploy recipe output files to live target machine',
scripts/lib/devtool/deploy.py def register_commands(subparsers, context): · line 291
Quoted 10 lines
scripts/lib/devtool/deploy.py +3
-1- description='Deploys a recipe\'s build output (i.e. the output of the do_install task) to a live target machine over ssh. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', + description='Deploys a recipe\'s build output (i.e. the output of the do_install task) to a live target machine over ssh. By default, any existing files will be preserved instead of being overwritten and will be restored if you run devtool undeploy-target. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', group='testbuild') parser_deploy.add_argument('recipename', help='Recipe to deploy') parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]') parser_deploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true') parser_deploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true') parser_deploy.add_argument('-n', '--dry-run', help='List files to be deployed only', action='store_true') + parser_deploy.add_argument('-p', '--no-preserve', help='Do not preserve existing files', action='store_true') + parser_deploy.add_argument('--no-check-space', help='Do not check for available space before deploying', action='store_true')
I think this should be single option, so everytime you deploy to target we should check for space before making changes this is required even when deploying with out preserving, thinking of a case where I might build the binary with debug info
or a new version which is large in size and won't fit into rootfs
Quoted 11 lines
scripts/lib/devtool/deploy.py +0
-1parser_deploy.set_defaults(func=deploy) parser_undeploy = subparsers.add_parser('undeploy-target', --2.5.0
--
_______________________________________________
Openembedded-core mailing list
[email protected]
http://lists.openembedded.org/mailman/listinfo/openembedded-core
Re: [PATCH 11/14] devtool: deploy-target: preserve existing files
Hi Khem,
Quoted 24 lines · On Sat, 20 Feb 2016 11:20:42 Khem Raj wrote:
Quoted 22 lines · Paul Eggleton
If files would be overwritten by the deployment, preserve them in a separate location on the target so that they can be restored if you later run devtool undeploy-target.
At the same time, also check for sufficient space before starting the operation so that we avoid potentially failing part way through.
Fixes [YOCTO #8978].
Signed-off-by Paul Eggleton <[email protected]>---
scripts/lib/devtool/deploy.py | 87++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-)
scripts/lib/devtool/deploy.py +0
-0@@ -28,7 +28,7 @@ logger = logging.getLogger('devtool')
Quoted 5 lines
Quoted 5 lines
scripts/lib/devtool/deploy.py +0
-1deploylist_path = '/.devtool' -def _prepare_remote_script(deploy, verbose=False, dryrun=False,undeployall=False):>
+def _prepare_remote_script(deploy, verbose=False, dryrun=False,
undeployall=False, nopreserve=False, nocheckspace=False):
Quoted 157 lines
Quoted 157 lines
"""
Prepare a shell script for running on the target to deploy/undeploy files. We have to be careful what we put in thisscripts/lib/devtool/deploy.py +0
-0@@ -48,6 +48,7 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
if not deploy:
lines.append('echo "Previously deployed files for $1:"')
lines.append('manifest="%s/$1.list"' % deploylist_path)scripts/lib/devtool/deploy.py +1
-0+ lines.append('preservedir="%s/$1.preserve"' % deploylist_path) lines.append('if [ -f $manifest ] ; then') # Read manifest in reverse and delete files / remove empty dirs lines.append(' sed \'1!G;h;$!d\' $manifest | while read file')@@ -58,7 +59,10 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
lines.append(' fi')else:
lines.append(' if [ -d $file ] ; then')scripts/lib/devtool/deploy.py +1
-1- lines.append(' rmdir $file > /dev/null 2>&1 || true') + # Avoid deleting a preserved directory in case it has specialperms + lines.append(' if [ ! -d $preservedir/$file ] ;
then') + lines.append(' rmdir $file >/dev/null
2>&1 || true') +lines.append(' fi')
lines.append(' else')
lines.append(' rm $file')
lines.append(' fi')scripts/lib/devtool/deploy.py +0
-0@@ -71,6 +75,39 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
lines.append('fi') if deploy:scripts/lib/devtool/deploy.py +3
-0+ if not nocheckspace: + # Check for available space + # FIXME This doesn't take into account files spread acrossmultiple + # partitions, but doing that is non-trivial
scripts/lib/devtool/deploy.py +3
-0+ # Find the part of the destination path that exists + lines.append('checkpath="$2"') + lines.append('while [ "$checkpath" != "/" ] && [ ! -e$checkpath ]') +
lines.append('do')scripts/lib/devtool/deploy.py +3
-0+ lines.append(' checkpath=`dirname "$checkpath"`') + lines.append('done') + lines.append('freespace=`df -P $checkpath | sed "1d" | awk\'{ print $4 }\'`') + # First line of the file is the total
spacescripts/lib/devtool/deploy.py +3
-0+ lines.append('total=`head -n1 $3`') + lines.append('if [ $total -gt $freespace ] ; then') + lines.append(' echo "ERROR: insufficient space on target(available ${freespace}, needed ${total})"') + lines.append('
exit 1')scripts/lib/devtool/deploy.py +3
-0+ lines.append('fi') + if not nopreserve: + # Preserve any files that exist. Note that this will add tothe + # preserved list with successive deployments if the list
of files + # deployed changes, but because we've deleted any
previously + # deployed files at this point it will never
preserve anything + # that was deployed, only files that
existed prior to any deploying + # (which makes the most
sense)scripts/lib/devtool/deploy.py +9
-0+ lines.append('cat $3 | sed "1d" | while read file fsize') + lines.append('do') + lines.append(' if [ -e $file ] ; then') + lines.append(' dest="$preservedir/$file"') + lines.append(' mkdir -p `dirname $dest`') + lines.append(' mv $file $dest') + lines.append(' fi') + lines.append('done') + lines.append('rm $3') lines.append('mkdir -p `dirname $manifest`') lines.append('mkdir -p $2') if verbose:@@ -78,6 +115,14 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
else:
lines.append(' tar xv -C $2 -f - > $manifest')
lines.append('sed -i "s!^./!$2!" $manifest')scripts/lib/devtool/deploy.py +8
-0+ elif not dryrun: + # Put any preserved files back + lines.append('if [ -d $preservedir ] ; then') + lines.append(' cd $preservedir') + lines.append(' find . -type f -exec mv {} /{} \;') + lines.append(' cd /') + lines.append(' rm -rf $preservedir') + lines.append('fi') if undeployall: if not dryrun:@@ -94,6 +139,7 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
defdeploy(args, config, basepath, workspace):
"""Entry point for the devtool 'deploy' subcommand""" import rescripts/lib/devtool/deploy.py +6
-0+ import math import oe.recipeutils check_workspace_recipe(workspace, args.recipename, checksrc=False)@@ -119,11 +165,23 @@ def deploy(args, config, basepath, workspace): 'recipe? If so, the install step has not installed ' 'any files.' % args.recipename) + filelist = [] + ftotalsize = 0 + for root, _, files in os.walk(recipe_outdir): + for fn in files: + # Get the size in kiB (since we'll be comparing it to theoutput of du -k) + # MUST use
lstat()here notstat()orgetfilesize()since we don't want to + # dereference symlinks
+ fsize = int(math.ceil(float(os.lstat(os.path.join(root,
fn)).st_size)/1024)) + ftotalsize += fsizescripts/lib/devtool/deploy.py +2
-0+ # The path as it would appear on the target + fpath = os.path.join(destdir, os.path.relpath(root,
recipe_outdir), fn) +filelist.append((fpath, fsize))scripts/lib/devtool/deploy.py +1
-3+ if args.dry_run: print('Files to be deployed for %s on target %s:' % (args.recipename, args.target))> - for root, _, files in os.walk(recipe_outdir): - for fn in files: - print(' %s' % os.path.join(destdir,
os.path.relpath(root, recipe_outdir), fn)) + for item, _ in
filelist:scripts/lib/devtool/deploy.py +1
-1+ print(' %s' % item) return 0@@ -140,11 +198,20 @@ def deploy(args, config, basepath, workspace): tmpdir = tempfile.mkdtemp(prefix='devtool') try: tmpscript = '/tmp/devtool_deploy.sh' - shellscript = _prepare_remote_script(deploy=True,verbose=args.show_status) + tmpfilelist =
os.path.join(os.path.dirname(tmpscript), 'devtool_deploy.list')+ shellscript = _prepare_remote_script(deploy=True,scripts/lib/devtool/deploy.py +3
-0+ verbose=args.show_status, + nopreserve=args.no_preserve, +nocheckspace=args.no_check_space)>
# Write out the script to a file
Quoted 1 line
Quoted 1 line
with
open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w')
as f:
Quoted 63 lines
Quoted 63 lines
f.write(shellscript)scripts/lib/devtool/deploy.py +2
-1- # Copy it to the target + # Write out the file list + with open(os.path.join(tmpdir, os.path.basename(tmpfilelist)),'w') as f: +
f.write('%d\n' % ftotalsize)scripts/lib/devtool/deploy.py +3
-1+ for fpath, fsize in filelist: + f.write('%s %d\n' % (fpath, fsize)) + # Copy them to the target ret = subprocess.call("scp %s %s/* %s:%s" % (extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True)> if ret != 0: raise DevtoolError('Failed to copy script to %s - rerun with -s to '@@ -153,7 +220,7 @@ def deploy(args, config, basepath, workspace): shutil.rmtree(tmpdir) # Now run the script - ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir),cwd=recipe_outdir, shell=True) + ret = exec_fakeroot(rd, 'tar cf - . |
ssh %s %s \'sh %s %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir, tmpfilelist), cwd=recipe_outdir, shell=True)>
if ret != 0:
raise DevtoolError('Deploy failed - rerun with -s to get a complete ''error message')
scripts/lib/devtool/deploy.py +0
-1@@ -213,13 +280,15 @@ def register_commands(subparsers, context): """Register devtool subcommands from the deploy plugin""" parser_deploy = subparsers.add_parser('deploy-target', help='Deploy recipe output files to live target machine', - description='Deploys arecipe\'s build output (i.e. the output of the
do_installtask) to a live target machine over ssh. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', +
description='Deploys a recipe\'s build output (i.e. the output of
thedo_installtask) to a live target machine over ssh. By default, any existing files will be preserved instead of being overwritten and will be restored if you run devtool undeploy-target. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.',>
group='testbuild')
parser_deploy.add_argument('recipename', help='Recipe to deploy')parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]')parser_deploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true')parser_deploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true')parser_deploy.add_argument('-n', '--dry-run', help='List files to be deployed only', action='store_true')>
+ parser_deploy.add_argument('-p', '--no-preserve', help='Do not
preserve existing files', action='store_true')
Quoted 8 lines
Quoted 2 lines
+ parser_deploy.add_argument('--no-check-space', help='Do not check for
available space before deploying', action='store_true')I think this should be single option, so everytime you deploy to target we should check for space before making changes this is required even when deploying with out preserving, thinking of a case where I might build the binary with debug info
or a new version which is large in size and won't fit into rootfs
I'm not sure I understand what you're saying. The default is to always check for available space; if it were one option then it would not be possible to deploy without preserving but still check for available space.
Cheers,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre
Re: [PATCH 01/14] devtool: minor fix for error message
In reply to Khem RajQuoted 17 lines · On Sat, 20 Feb 2016 11:16:32 Khem Raj wrote:
On Fri, Feb 19, 2016 at 1:38 AM, Paul Eggleton
Quoted 14 lines · <[email protected]> wrote:
There is no -N/--name option for devtool, that's a recipetool option - with devtool you just specify the name as a positional argument.
Signed-off-by Paul Eggleton <[email protected]>---
scripts/lib/devtool/standard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)(quoted diff) +0
-0
b/scripts/lib/devtool/standard.pyindex d12cc2e..590dfef 100644scripts/lib/devtool/standard.py +0
-0
Quoted 12 lines
Quoted 10 lines
scripts/lib/devtool/standard.py +0
-1@@ -150,7 +150,7 @@ def add(args, config, basepath, workspace): stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create -o %s "%s" %s' % (color, tempdir, source, extracmdopts))> except bb.process.ExecutionError as e: if e.exitcode == 15: - raise DevtoolError('Unable to auto-determine name fromsource tree, please specify it with -N/--name') + raise
DevtoolError('Unable to auto-determine recipe name from source tree, please specify it on the command line')may be reword it a bit to "Could not auto detect recipe name, please specify on commandline"
Right, that would be better wording. I'll send a follow-up patch.
Thanks,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre
Re: [PATCH 11/14] devtool: deploy-target: preserve existing files
In reply to Paul EggletonQuoted 264 lines · Paul Eggleton
Hi Khem,
Quoted 24 lines · On Sat, 20 Feb 2016 11:20:42 Khem Raj wrote:
Quoted 22 lines · Paul Eggleton
If files would be overwritten by the deployment, preserve them in a separate location on the target so that they can be restored if you later run devtool undeploy-target.
At the same time, also check for sufficient space before starting the operation so that we avoid potentially failing part way through.
Fixes [YOCTO #8978].
Signed-off-by Paul Eggleton <[email protected]>---
scripts/lib/devtool/deploy.py | 87++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-)
scripts/lib/devtool/deploy.py +0
-0@@ -28,7 +28,7 @@ logger = logging.getLogger('devtool')Quoted 5 lines
Quoted 5 lines
scripts/lib/devtool/deploy.py +0
-1deploylist_path = '/.devtool' -def _prepare_remote_script(deploy, verbose=False, dryrun=False,undeployall=False):>
+def _prepare_remote_script(deploy, verbose=False, dryrun=False,undeployall=False, nopreserve=False, nocheckspace=False):
Quoted 157 lines
Quoted 157 lines
"""
Prepare a shell script for running on the target to deploy/undeploy files. We have to be careful what we put in thisscripts/lib/devtool/deploy.py +0
-0@@ -48,6 +48,7 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
if not deploy:
lines.append('echo "Previously deployed files for $1:"')
lines.append('manifest="%s/$1.list"' % deploylist_path)scripts/lib/devtool/deploy.py +1
-0+ lines.append('preservedir="%s/$1.preserve"' % deploylist_path) lines.append('if [ -f $manifest ] ; then') # Read manifest in reverse and delete files / remove empty dirs lines.append(' sed \'1!G;h;$!d\' $manifest | while read file')@@ -58,7 +59,10 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
lines.append(' fi')else:
lines.append(' if [ -d $file ] ; then')scripts/lib/devtool/deploy.py +1
-1- lines.append(' rmdir $file > /dev/null 2>&1 || true') + # Avoid deleting a preserved directory in case it has specialperms + lines.append(' if [ ! -d $preservedir/$file ] ;
then') + lines.append(' rmdir $file >/dev/null
2>&1 || true') +lines.append(' fi')
lines.append(' else')
lines.append(' rm $file')
lines.append(' fi')scripts/lib/devtool/deploy.py +0
-0@@ -71,6 +75,39 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
lines.append('fi') if deploy:scripts/lib/devtool/deploy.py +3
-0+ if not nocheckspace: + # Check for available space + # FIXME This doesn't take into account files spread acrossmultiple + # partitions, but doing that is non-trivial
scripts/lib/devtool/deploy.py +3
-0+ # Find the part of the destination path that exists + lines.append('checkpath="$2"') + lines.append('while [ "$checkpath" != "/" ] && [ ! -e$checkpath ]') +
lines.append('do')scripts/lib/devtool/deploy.py +3
-0+ lines.append(' checkpath=`dirname "$checkpath"`') + lines.append('done') + lines.append('freespace=`df -P $checkpath | sed "1d" | awk\'{ print $4 }\'`') + # First line of the file is the total
spacescripts/lib/devtool/deploy.py +3
-0+ lines.append('total=`head -n1 $3`') + lines.append('if [ $total -gt $freespace ] ; then') + lines.append(' echo "ERROR: insufficient space on target(available ${freespace}, needed ${total})"') + lines.append('
exit 1')scripts/lib/devtool/deploy.py +3
-0+ lines.append('fi') + if not nopreserve: + # Preserve any files that exist. Note that this will add tothe + # preserved list with successive deployments if the list
of files + # deployed changes, but because we've deleted any
previously + # deployed files at this point it will never
preserve anything + # that was deployed, only files that
existed prior to any deploying + # (which makes the most
sense)scripts/lib/devtool/deploy.py +9
-0+ lines.append('cat $3 | sed "1d" | while read file fsize') + lines.append('do') + lines.append(' if [ -e $file ] ; then') + lines.append(' dest="$preservedir/$file"') + lines.append(' mkdir -p `dirname $dest`') + lines.append(' mv $file $dest') + lines.append(' fi') + lines.append('done') + lines.append('rm $3') lines.append('mkdir -p `dirname $manifest`') lines.append('mkdir -p $2') if verbose:@@ -78,6 +115,14 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
else:
lines.append(' tar xv -C $2 -f - > $manifest')
lines.append('sed -i "s!^./!$2!" $manifest')scripts/lib/devtool/deploy.py +8
-0+ elif not dryrun: + # Put any preserved files back + lines.append('if [ -d $preservedir ] ; then') + lines.append(' cd $preservedir') + lines.append(' find . -type f -exec mv {} /{} \;') + lines.append(' cd /') + lines.append(' rm -rf $preservedir') + lines.append('fi') if undeployall: if not dryrun:@@ -94,6 +139,7 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
defdeploy(args, config, basepath, workspace):
"""Entry point for the devtool 'deploy' subcommand""" import rescripts/lib/devtool/deploy.py +6
-0+ import math import oe.recipeutils check_workspace_recipe(workspace, args.recipename, checksrc=False)@@ -119,11 +165,23 @@ def deploy(args, config, basepath, workspace): 'recipe? If so, the install step has not installed ' 'any files.' % args.recipename) + filelist = [] + ftotalsize = 0 + for root, _, files in os.walk(recipe_outdir): + for fn in files: + # Get the size in kiB (since we'll be comparing it to theoutput of du -k) + # MUST use
lstat()here notstat()orgetfilesize()since we don't want to + # dereference symlinks
+ fsize = int(math.ceil(float(os.lstat(os.path.join(root,
fn)).st_size)/1024)) + ftotalsize += fsizescripts/lib/devtool/deploy.py +2
-0+ # The path as it would appear on the target + fpath = os.path.join(destdir, os.path.relpath(root,
recipe_outdir), fn) +filelist.append((fpath, fsize))scripts/lib/devtool/deploy.py +1
-3+ if args.dry_run: print('Files to be deployed for %s on target %s:' % (args.recipename, args.target))> - for root, _, files in os.walk(recipe_outdir): - for fn in files: - print(' %s' % os.path.join(destdir,
os.path.relpath(root, recipe_outdir), fn)) + for item, _ in
filelist:scripts/lib/devtool/deploy.py +1
-1+ print(' %s' % item) return 0@@ -140,11 +198,20 @@ def deploy(args, config, basepath, workspace): tmpdir = tempfile.mkdtemp(prefix='devtool') try: tmpscript = '/tmp/devtool_deploy.sh' - shellscript = _prepare_remote_script(deploy=True,verbose=args.show_status) + tmpfilelist =
os.path.join(os.path.dirname(tmpscript), 'devtool_deploy.list')+ shellscript = _prepare_remote_script(deploy=True,scripts/lib/devtool/deploy.py +3
-0+ verbose=args.show_status, + nopreserve=args.no_preserve, +nocheckspace=args.no_check_space)>
# Write out the script to a fileQuoted 1 line
Quoted 1 line
with
open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w')as f:
Quoted 63 lines
Quoted 63 lines
f.write(shellscript)scripts/lib/devtool/deploy.py +2
-1- # Copy it to the target + # Write out the file list + with open(os.path.join(tmpdir, os.path.basename(tmpfilelist)),'w') as f: +
f.write('%d\n' % ftotalsize)scripts/lib/devtool/deploy.py +3
-1+ for fpath, fsize in filelist: + f.write('%s %d\n' % (fpath, fsize)) + # Copy them to the target ret = subprocess.call("scp %s %s/* %s:%s" % (extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True)> if ret != 0: raise DevtoolError('Failed to copy script to %s - rerun with -s to '@@ -153,7 +220,7 @@ def deploy(args, config, basepath, workspace): shutil.rmtree(tmpdir) # Now run the script - ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir),cwd=recipe_outdir, shell=True) + ret = exec_fakeroot(rd, 'tar cf - . |
ssh %s %s \'sh %s %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir, tmpfilelist), cwd=recipe_outdir, shell=True)>
if ret != 0:
raise DevtoolError('Deploy failed - rerun with -s to get a complete ''error message')
scripts/lib/devtool/deploy.py +0
-1@@ -213,13 +280,15 @@ def register_commands(subparsers, context): """Register devtool subcommands from the deploy plugin""" parser_deploy = subparsers.add_parser('deploy-target', help='Deploy recipe output files to live target machine', - description='Deploys arecipe\'s build output (i.e. the output of the
do_installtask) to a live target machine over ssh. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', +
description='Deploys a recipe\'s build output (i.e. the output of
thedo_installtask) to a live target machine over ssh. By default, any existing files will be preserved instead of being overwritten and will be restored if you run devtool undeploy-target. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.',>
group='testbuild')
parser_deploy.add_argument('recipename', help='Recipe to deploy')parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]')parser_deploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true')parser_deploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true')parser_deploy.add_argument('-n', '--dry-run', help='List files to be deployed only', action='store_true')>
+ parser_deploy.add_argument('-p', '--no-preserve', help='Do not
preserve existing files', action='store_true')Quoted 8 lines
Quoted 2 lines
+ parser_deploy.add_argument('--no-check-space', help='Do not check for
available space before deploying', action='store_true')I think this should be single option, so everytime you deploy to target we should check for space before making changes this is required even when deploying with out preserving, thinking of a case where I might build the binary with debug info
or a new version which is large in size and won't fit into rootfs
Quoted 3 lines
I'm not sure I understand what you're saying. The default is to always check for available space; if it were one option then it would not be possible to deploy without preserving but still check for available space.
I was meaning to say that option to not check space is redundant.
Quoted 8 lines
Cheers,
Paul--
Paul Eggleton
Intel Open Source Technology Centre
Re: [PATCH 11/14] devtool: deploy-target: preserve existing files
Quoted 304 lines · On Sun, 21 Feb 2016 12:03:38 Khem Raj wrote:
On Sun, Feb 21, 2016 at 11:30 AM, Paul Eggleton
Quoted 301 lines · <[email protected]> wrote:
Hi Khem,
Quoted 26 lines · On Sat, 20 Feb 2016 11:20:42 Khem Raj wrote:
On Fri, Feb 19, 2016 at 1:38 AM, Paul Eggleton
Quoted 23 lines · <[email protected]> wrote:
If files would be overwritten by the deployment, preserve them in a separate location on the target so that they can be restored if you later run devtool undeploy-target.
At the same time, also check for sufficient space before starting the operation so that we avoid potentially failing part way through.
Fixes [YOCTO #8978].
Signed-off-by Paul Eggleton <[email protected]>---
scripts/lib/devtool/deploy.py | 87++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-)
scripts/lib/devtool/deploy.py +0
-0@@ -28,7 +28,7 @@ logger = logging.getLogger('devtool')Quoted 5 lines
Quoted 5 lines
scripts/lib/devtool/deploy.py +0
-1deploylist_path = '/.devtool' -def _prepare_remote_script(deploy, verbose=False, dryrun=False,undeployall=False):>
+def _prepare_remote_script(deploy, verbose=False, dryrun=False,undeployall=False, nopreserve=False, nocheckspace=False):
Quoted 172 lines
Quoted 172 lines
"""
Prepare a shell script for running on the target to deploy/undeploy files. We have to be careful what we put in thisscripts/lib/devtool/deploy.py +0
-0@@ -48,6 +48,7 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
if not deploy:
lines.append('echo "Previously deployed files for $1:"')
lines.append('manifest="%s/$1.list"' % deploylist_path)scripts/lib/devtool/deploy.py +1
-0+ lines.append('preservedir="%s/$1.preserve"' % deploylist_path) lines.append('if [ -f $manifest ] ; then') # Read manifest in reverse and delete files / remove empty dirs lines.append(' sed \'1!G;h;$!d\' $manifest | while read file')@@ -58,7 +59,10 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
lines.append(' fi')else:
lines.append(' if [ -d $file ] ; then')- lines.append(' rmdir $file >
/dev/null2>&1 ||
true')
+ # Avoid deleting a preserved directory in case it has special
perms + lines.append(' if [ ! -d $preservedir/$file ]
;
then') + lines.append(' rmdir $file >/dev/null
2>&1 || true') +lines.append(' fi')
lines.append(' else')
lines.append(' rm $file')
lines.append(' fi')scripts/lib/devtool/deploy.py +0
-0@@ -71,6 +75,39 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
lines.append('fi') if deploy:scripts/lib/devtool/deploy.py +3
-0+ if not nocheckspace: + # Check for available space + # FIXME This doesn't take into account files spread acrossmultiple + # partitions, but doing that is non-trivial
scripts/lib/devtool/deploy.py +3
-0+ # Find the part of the destination path that exists + lines.append('checkpath="$2"') + lines.append('while [ "$checkpath" != "/" ] && [ ! -e$checkpath ]') +
lines.append('do')scripts/lib/devtool/deploy.py +3
-0+ lines.append(' checkpath=`dirname "$checkpath"`') + lines.append('done') + lines.append('freespace=`df -P $checkpath | sed "1d" | awk\'{ print $4 }\'`') + # First line of the file is the total
spacescripts/lib/devtool/deploy.py +3
-0+ lines.append('total=`head -n1 $3`') + lines.append('if [ $total -gt $freespace ] ; then') + lines.append(' echo "ERROR: insufficient space ontarget
(available ${freespace}, needed ${total})"') +
lines.append('exit 1')
scripts/lib/devtool/deploy.py +3
-0+ lines.append('fi') + if not nopreserve: + # Preserve any files that exist. Note that this will addto
the + # preserved list with successive deployments if the
list
of files + # deployed changes, but because we've deleted any
previously + # deployed files at this point it will never
preserve anything + # that was deployed, only files that
existed prior to any deploying + # (which makes the most
sense)scripts/lib/devtool/deploy.py +9
-0+ lines.append('cat $3 | sed "1d" | while read file fsize') + lines.append('do') + lines.append(' if [ -e $file ] ; then') + lines.append(' dest="$preservedir/$file"') + lines.append(' mkdir -p `dirname $dest`') + lines.append(' mv $file $dest') + lines.append(' fi') + lines.append('done') + lines.append('rm $3') lines.append('mkdir -p `dirname $manifest`') lines.append('mkdir -p $2') if verbose:@@ -78,6 +115,14 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
else:
lines.append(' tar xv -C $2 -f - > $manifest')
lines.append('sed -i "s!^./!$2!" $manifest')scripts/lib/devtool/deploy.py +8
-0+ elif not dryrun: + # Put any preserved files back + lines.append('if [ -d $preservedir ] ; then') + lines.append(' cd $preservedir') + lines.append(' find . -type f -exec mv {} /{} \;') + lines.append(' cd /') + lines.append(' rm -rf $preservedir') + lines.append('fi') if undeployall: if not dryrun:@@ -94,6 +139,7 @@ def _prepare_remote_script(deploy, verbose=False,dryrun=False, undeployall=Fals>
def
deploy(args, config, basepath, workspace):
"""Entry point for the devtool 'deploy' subcommand""" import rescripts/lib/devtool/deploy.py +6
-0+ import math import oe.recipeutils check_workspace_recipe(workspace, args.recipename, checksrc=False)@@ -119,11 +165,23 @@ def deploy(args, config, basepath, workspace): 'recipe? If so, the install step has not installed ' 'any files.' % args.recipename) + filelist = [] + ftotalsize = 0 + for root, _, files in os.walk(recipe_outdir): + for fn in files: + # Get the size in kiB (since we'll be comparing it to theoutput of du -k) + # MUST use
lstat()here notstat()orgetfilesize()since we don't want to + # dereference
symlinks
+ fsize = int(math.ceil(float(os.lstat(os.path.join(root,
fn)).st_size)/1024)) + ftotalsize += fsizescripts/lib/devtool/deploy.py +2
-0+ # The path as it would appear on the target + fpath = os.path.join(destdir, os.path.relpath(root,
recipe_outdir), fn) +filelist.append((fpath, fsize))scripts/lib/devtool/deploy.py +1
-3+ if args.dry_run: print('Files to be deployed for %s on target %s:' % (args.recipename, args.target))> - for root, _, files in os.walk(recipe_outdir): - for fn in files: - print(' %s' % os.path.join(destdir,
os.path.relpath(root, recipe_outdir), fn)) + for item, _ in
filelist:scripts/lib/devtool/deploy.py +1
-1+ print(' %s' % item) return 0@@ -140,11 +198,20 @@ def deploy(args, config, basepath, workspace): tmpdir = tempfile.mkdtemp(prefix='devtool') try: tmpscript = '/tmp/devtool_deploy.sh' - shellscript = _prepare_remote_script(deploy=True,verbose=args.show_status) + tmpfilelist =
os.path.join(os.path.dirname(tmpscript), 'devtool_deploy.list')+ shellscript = _prepare_remote_script(deploy=True,scripts/lib/devtool/deploy.py +2
-0+ verbose=args.show_status, +nopreserve=args.no_preserve,
+
nocheckspace=args.no_check_space)>Quoted 4 lines
Quoted 4 lines
# Write out the script to a file
with
open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w')as f:
Quoted 86 lines
Quoted 75 lines
f.write(shellscript)scripts/lib/devtool/deploy.py +2
-1- # Copy it to the target + # Write out the file list + with open(os.path.join(tmpdir, os.path.basename(tmpfilelist)),'w') as f: +
f.write('%d\n' % ftotalsize)scripts/lib/devtool/deploy.py +3
-1+ for fpath, fsize in filelist: + f.write('%s %d\n' % (fpath, fsize)) + # Copy them to the target ret = subprocess.call("scp %s %s/* %s:%s" % (extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True)> if ret != 0: raise DevtoolError('Failed to copy script to %s - rerun with -s to '@@ -153,7 +220,7 @@ def deploy(args, config, basepath, workspace): shutil.rmtree(tmpdir) # Now run the script - ret = exec_fakeroot(rd, 'tar cf - . | ssh %s %s \'sh %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir),cwd=recipe_outdir, shell=True) + ret = exec_fakeroot(rd, 'tar cf - .
|
ssh %s %s \'sh %s %s %s %s\'' % (extraoptions, args.target, tmpscript, args.recipename, destdir, tmpfilelist), cwd=recipe_outdir, shell=True)>if ret != 0:
raise DevtoolError('Deploy failed - rerun with -s to get a complete ''error message')
scripts/lib/devtool/deploy.py +0
-1@@ -213,13 +280,15 @@ def register_commands(subparsers, context): """Register devtool subcommands from the deploy plugin""" parser_deploy = subparsers.add_parser('deploy-target', help='Deploy recipe output files to live target machine', - description='Deploys arecipe\'s build output (i.e. the output of the
do_installtask) to a live
target machine over ssh. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.', +description='Deploys a recipe\'s build output (i.e. the output of
the
do_installtask) to a live target machine over ssh. By default, any existing files will be preserved instead of being overwritten and will be
restored if you run devtool undeploy-target. Note: this only deploys the
recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.',>group='testbuild')
parser_deploy.add_argument('recipename', help='Recipe to deploy')parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]')parser_deploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true')parser_deploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true')parser_deploy.add_argument('-n', '--dry-run', help='List files to be
deployed only', action='store_true')>Quoted 5 lines
+ parser_deploy.add_argument('-p', '--no-preserve', help='Do not
preserve existing files', action='store_true')
+ parser_deploy.add_argument('--no-check-space', help='Do not check
for
available space before deploying', action='store_true')I think this should be single option, so everytime you deploy to target we should check for space before making changes this is required even when deploying with out preserving, thinking of a case where I might build the binary with debug info
or a new version which is large in size and won't fit into rootfs
Quoted 6 lines
Quoted 4 lines
I'm not sure I understand what you're saying. The default is to always check for available space; if it were one option then it would not be possible to deploy without preserving but still check for available space.
I was meaning to say that option to not check space is redundant.
Not entirely - the check for space is slightly naive, in that it assumes only one filesystem is going to be written to - which will be true in a high proportion of systems I would assume. Doing otherwise would be complicated in the shell script on the target especially if you throw in preserving files across multiple filesystems as well, which you'd need to if you wanted to be completely accurate. Thus I added the option to avoid blocking things in case you know better than it does.
Cheers,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre