From b5dd8c128624cb77576d692b68e24691d4d9a96d Mon Sep 17 00:00:00 2001 From: John Klug Date: Tue, 31 Jul 2018 17:48:08 -0500 Subject: mLinux 4 --- scripts/lib/devtool/__init__.py | 257 +++++++ scripts/lib/devtool/build.py | 86 +++ scripts/lib/devtool/build_image.py | 168 +++++ scripts/lib/devtool/build_sdk.py | 65 ++ scripts/lib/devtool/deploy.py | 304 ++++++++ scripts/lib/devtool/package.py | 62 ++ scripts/lib/devtool/runqemu.py | 65 ++ scripts/lib/devtool/sdk.py | 366 +++++++++ scripts/lib/devtool/search.py | 88 +++ scripts/lib/devtool/standard.py | 1452 ++++++++++++++++++++++++++++++++++++ scripts/lib/devtool/upgrade.py | 382 ++++++++++ scripts/lib/devtool/utilcmds.py | 233 ++++++ 12 files changed, 3528 insertions(+) create mode 100644 scripts/lib/devtool/__init__.py create mode 100644 scripts/lib/devtool/build.py create mode 100644 scripts/lib/devtool/build_image.py create mode 100644 scripts/lib/devtool/build_sdk.py create mode 100644 scripts/lib/devtool/deploy.py create mode 100644 scripts/lib/devtool/package.py create mode 100644 scripts/lib/devtool/runqemu.py create mode 100644 scripts/lib/devtool/sdk.py create mode 100644 scripts/lib/devtool/search.py create mode 100644 scripts/lib/devtool/standard.py create mode 100644 scripts/lib/devtool/upgrade.py create mode 100644 scripts/lib/devtool/utilcmds.py (limited to 'scripts/lib/devtool') diff --git a/scripts/lib/devtool/__init__.py b/scripts/lib/devtool/__init__.py new file mode 100644 index 0000000..ff97dfc --- /dev/null +++ b/scripts/lib/devtool/__init__.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python + +# Development tool - utility functions for plugins +# +# Copyright (C) 2014 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Devtool plugins module""" + +import os +import sys +import subprocess +import logging +import re + +logger = logging.getLogger('devtool') + + +class DevtoolError(Exception): + """Exception for handling devtool errors""" + pass + + +def exec_build_env_command(init_path, builddir, cmd, watch=False, **options): + """Run a program in bitbake build context""" + import bb + if not 'cwd' in options: + options["cwd"] = builddir + if init_path: + # As the OE init script makes use of BASH_SOURCE to determine OEROOT, + # and can't determine it when running under dash, we need to set + # the executable to bash to correctly set things up + if not 'executable' in options: + options['executable'] = 'bash' + logger.debug('Executing command: "%s" using init path %s' % (cmd, init_path)) + init_prefix = '. %s %s > /dev/null && ' % (init_path, builddir) + else: + logger.debug('Executing command "%s"' % cmd) + init_prefix = '' + if watch: + if sys.stdout.isatty(): + # Fool bitbake into thinking it's outputting to a terminal (because it is, indirectly) + cmd = 'script -e -q -c "%s" /dev/null' % cmd + return exec_watch('%s%s' % (init_prefix, cmd), **options) + else: + return bb.process.run('%s%s' % (init_prefix, cmd), **options) + +def exec_watch(cmd, **options): + """Run program with stdout shown on sys.stdout""" + import bb + if isinstance(cmd, basestring) and not "shell" in options: + options["shell"] = True + + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **options + ) + + buf = '' + while True: + out = process.stdout.read(1) + if out: + sys.stdout.write(out) + sys.stdout.flush() + buf += out + elif out == '' and process.poll() != None: + break + + if process.returncode != 0: + raise bb.process.ExecutionError(cmd, process.returncode, buf, None) + + return buf, None + +def exec_fakeroot(d, cmd, **kwargs): + """Run a command under fakeroot (pseudo, in fact) so that it picks up the appropriate file permissions""" + # Grab the command and check it actually exists + fakerootcmd = d.getVar('FAKEROOTCMD', True) + if not os.path.exists(fakerootcmd): + logger.error('pseudo executable %s could not be found - have you run a build yet? pseudo-native should install this and if you have run any build then that should have been built') + return 2 + # Set up the appropriate environment + newenv = dict(os.environ) + fakerootenv = d.getVar('FAKEROOTENV', True) + for varvalue in fakerootenv.split(): + if '=' in varvalue: + splitval = varvalue.split('=', 1) + newenv[splitval[0]] = splitval[1] + return subprocess.call("%s %s" % (fakerootcmd, cmd), env=newenv, **kwargs) + +def setup_tinfoil(config_only=False, basepath=None, tracking=False): + """Initialize tinfoil api from bitbake""" + import scriptpath + orig_cwd = os.path.abspath(os.curdir) + try: + if basepath: + os.chdir(basepath) + bitbakepath = scriptpath.add_bitbake_lib_path() + if not bitbakepath: + logger.error("Unable to find bitbake by searching parent directory of this script or PATH") + sys.exit(1) + + import bb.tinfoil + tinfoil = bb.tinfoil.Tinfoil(tracking=tracking) + tinfoil.prepare(config_only) + tinfoil.logger.setLevel(logger.getEffectiveLevel()) + finally: + os.chdir(orig_cwd) + return tinfoil + +def get_recipe_file(cooker, pn): + """Find recipe file corresponding a package name""" + import oe.recipeutils + recipefile = oe.recipeutils.pn_to_recipe(cooker, pn) + if not recipefile: + skipreasons = oe.recipeutils.get_unavailable_reasons(cooker, pn) + if skipreasons: + logger.error('\n'.join(skipreasons)) + else: + logger.error("Unable to find any recipe file matching %s" % pn) + return recipefile + +def parse_recipe(config, tinfoil, pn, appends, filter_workspace=True): + """Parse recipe of a package""" + import oe.recipeutils + recipefile = get_recipe_file(tinfoil.cooker, pn) + if not recipefile: + # Error already logged + return None + if appends: + append_files = tinfoil.cooker.collection.get_file_appends(recipefile) + if filter_workspace: + # Filter out appends from the workspace + append_files = [path for path in append_files if + not path.startswith(config.workspace_path)] + else: + append_files = None + return oe.recipeutils.parse_recipe(recipefile, append_files, + tinfoil.config_data) + +def check_workspace_recipe(workspace, pn, checksrc=True, bbclassextend=False): + """ + Check that a recipe is in the workspace and (optionally) that source + is present. + """ + + workspacepn = pn + + for recipe, value in workspace.iteritems(): + if recipe == pn: + break + if bbclassextend: + recipefile = value['recipefile'] + if recipefile: + targets = get_bbclassextend_targets(recipefile, recipe) + if pn in targets: + workspacepn = recipe + break + else: + raise DevtoolError("No recipe named '%s' in your workspace" % pn) + + if checksrc: + srctree = workspace[workspacepn]['srctree'] + if not os.path.exists(srctree): + raise DevtoolError("Source tree %s for recipe %s does not exist" % (srctree, workspacepn)) + if not os.listdir(srctree): + raise DevtoolError("Source tree %s for recipe %s is empty" % (srctree, workspacepn)) + + return workspacepn + +def use_external_build(same_dir, no_same_dir, d): + """ + Determine if we should use B!=S (separate build and source directories) or not + """ + b_is_s = True + if no_same_dir: + logger.info('Using separate build directory since --no-same-dir specified') + b_is_s = False + elif same_dir: + logger.info('Using source tree as build directory since --same-dir specified') + elif bb.data.inherits_class('autotools-brokensep', d): + logger.info('Using source tree as build directory since recipe inherits autotools-brokensep') + elif d.getVar('B', True) == os.path.abspath(d.getVar('S', True)): + logger.info('Using source tree as build directory since that would be the default for this recipe') + else: + b_is_s = False + return b_is_s + +def setup_git_repo(repodir, version, devbranch, basetag='devtool-base'): + """ + Set up the git repository for the source tree + """ + import bb.process + if not os.path.exists(os.path.join(repodir, '.git')): + bb.process.run('git init', cwd=repodir) + bb.process.run('git add .', cwd=repodir) + commit_cmd = ['git', 'commit', '-q'] + stdout, _ = bb.process.run('git status --porcelain', cwd=repodir) + if not stdout: + commit_cmd.append('--allow-empty') + commitmsg = "Initial empty commit with no upstream sources" + elif version: + commitmsg = "Initial commit from upstream at version %s" % version + else: + commitmsg = "Initial commit from upstream" + commit_cmd += ['-m', commitmsg] + bb.process.run(commit_cmd, cwd=repodir) + + bb.process.run('git checkout -b %s' % devbranch, cwd=repodir) + bb.process.run('git tag -f %s' % basetag, cwd=repodir) + +def recipe_to_append(recipefile, config, wildcard=False): + """ + Convert a recipe file to a bbappend file path within the workspace. + NOTE: if the bbappend already exists, you should be using + workspace[args.recipename]['bbappend'] instead of calling this + function. + """ + appendname = os.path.splitext(os.path.basename(recipefile))[0] + if wildcard: + appendname = re.sub(r'_.*', '_%', appendname) + appendpath = os.path.join(config.workspace_path, 'appends') + appendfile = os.path.join(appendpath, appendname + '.bbappend') + return appendfile + +def get_bbclassextend_targets(recipefile, pn): + """ + Cheap function to get BBCLASSEXTEND and then convert that to the + list of targets that would result. + """ + import bb.utils + + values = {} + def get_bbclassextend_varfunc(varname, origvalue, op, newlines): + values[varname] = origvalue + return origvalue, None, 0, True + with open(recipefile, 'r') as f: + bb.utils.edit_metadata(f, ['BBCLASSEXTEND'], get_bbclassextend_varfunc) + + targets = [] + bbclassextend = values.get('BBCLASSEXTEND', '').split() + if bbclassextend: + for variant in bbclassextend: + if variant == 'nativesdk': + targets.append('%s-%s' % (variant, pn)) + elif variant in ['native', 'cross', 'crosssdk']: + targets.append('%s-%s' % (pn, variant)) + return targets diff --git a/scripts/lib/devtool/build.py b/scripts/lib/devtool/build.py new file mode 100644 index 0000000..48f6fe1 --- /dev/null +++ b/scripts/lib/devtool/build.py @@ -0,0 +1,86 @@ +# Development tool - build command plugin +# +# Copyright (C) 2014-2015 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Devtool build plugin""" + +import os +import bb +import logging +import argparse +import tempfile +from devtool import exec_build_env_command, check_workspace_recipe, DevtoolError + +logger = logging.getLogger('devtool') + + +def _set_file_values(fn, values): + remaining = values.keys() + + def varfunc(varname, origvalue, op, newlines): + newvalue = values.get(varname, origvalue) + remaining.remove(varname) + return (newvalue, '=', 0, True) + + with open(fn, 'r') as f: + (updated, newlines) = bb.utils.edit_metadata(f, values, varfunc) + + for item in remaining: + updated = True + newlines.append('%s = "%s"' % (item, values[item])) + + if updated: + with open(fn, 'w') as f: + f.writelines(newlines) + return updated + +def _get_build_tasks(config): + tasks = config.get('Build', 'build_task', 'populate_sysroot,packagedata').split(',') + return ['do_%s' % task.strip() for task in tasks] + +def build(args, config, basepath, workspace): + """Entry point for the devtool 'build' subcommand""" + workspacepn = check_workspace_recipe(workspace, args.recipename, bbclassextend=True) + + build_tasks = _get_build_tasks(config) + + bbappend = workspace[workspacepn]['bbappend'] + if args.disable_parallel_make: + logger.info("Disabling 'make' parallelism") + _set_file_values(bbappend, {'PARALLEL_MAKE': ''}) + try: + bbargs = [] + for task in build_tasks: + if args.recipename.endswith('-native') and 'package' in task: + continue + bbargs.append('%s:%s' % (args.recipename, task)) + exec_build_env_command(config.init_path, basepath, 'bitbake %s' % ' '.join(bbargs), watch=True) + except bb.process.ExecutionError as e: + # We've already seen the output since watch=True, so just ensure we return something to the user + return e.exitcode + finally: + if args.disable_parallel_make: + _set_file_values(bbappend, {'PARALLEL_MAKE': None}) + + return 0 + +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)), + 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) diff --git a/scripts/lib/devtool/build_image.py b/scripts/lib/devtool/build_image.py new file mode 100644 index 0000000..14c646a --- /dev/null +++ b/scripts/lib/devtool/build_image.py @@ -0,0 +1,168 @@ +# Development tool - build-image plugin +# +# Copyright (C) 2015 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Devtool plugin containing the build-image subcommand.""" + +import os +import errno +import logging + +from bb.process import ExecutionError +from devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError + +logger = logging.getLogger('devtool') + +class TargetNotImageError(Exception): + pass + +def _get_packages(tinfoil, workspace, config): + """Get list of packages from recipes in the workspace.""" + result = [] + for recipe in workspace: + data = parse_recipe(config, tinfoil, recipe, True) + if 'class-target' in data.getVar('OVERRIDES', True).split(':'): + if recipe in data.getVar('PACKAGES', True).split(): + result.append(recipe) + else: + logger.warning("Skipping recipe %s as it doesn't produce a " + "package with the same name", recipe) + return result + +def build_image(args, config, basepath, workspace): + """Entry point for the devtool 'build-image' subcommand.""" + + image = args.imagename + auto_image = False + if not image: + sdk_targets = config.get('SDK', 'sdk_targets', '').split() + if sdk_targets: + image = sdk_targets[0] + auto_image = True + if not image: + raise DevtoolError('Unable to determine image to build, please specify one') + + try: + if args.add_packages: + add_packages = args.add_packages.split(',') + else: + add_packages = None + result, outputdir = build_image_task(config, basepath, workspace, image, add_packages) + except TargetNotImageError: + if auto_image: + raise DevtoolError('Unable to determine image to build, please specify one') + else: + raise DevtoolError('Specified recipe %s is not an image recipe' % image) + + if result == 0: + logger.info('Successfully built %s. You can find output files in %s' + % (image, outputdir)) + return result + +def build_image_task(config, basepath, workspace, image, add_packages=None, task=None, extra_append=None): + # remove .bbappend to make sure setup_tinfoil doesn't + # break because of it + target_basename = config.get('SDK', 'target_basename', '') + if target_basename: + appendfile = os.path.join(config.workspace_path, 'appends', + '%s.bbappend' % target_basename) + try: + os.unlink(appendfile) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise + + tinfoil = setup_tinfoil(basepath=basepath) + rd = parse_recipe(config, tinfoil, image, True) + if not rd: + # Error already shown + return (1, None) + if not bb.data.inherits_class('image', rd): + raise TargetNotImageError() + + # Get the actual filename used and strip the .bb and full path + target_basename = rd.getVar('FILE', True) + target_basename = os.path.splitext(os.path.basename(target_basename))[0] + config.set('SDK', 'target_basename', target_basename) + config.write() + + appendfile = os.path.join(config.workspace_path, 'appends', + '%s.bbappend' % target_basename) + + outputdir = None + try: + if workspace or add_packages: + if add_packages: + packages = add_packages + else: + packages = _get_packages(tinfoil, workspace, config) + else: + packages = None + if not task: + if not packages and not add_packages and workspace: + logger.warning('No recipes in workspace, building image %s unmodified', image) + elif not packages: + logger.warning('No packages to add, building image %s unmodified', image) + + if packages or extra_append: + bb.utils.mkdirhier(os.path.dirname(appendfile)) + with open(appendfile, 'w') as afile: + if packages: + # include packages from workspace recipes into the image + afile.write('IMAGE_INSTALL_append = " %s"\n' % ' '.join(packages)) + if not task: + logger.info('Building image %s with the following ' + 'additional packages: %s', image, ' '.join(packages)) + if extra_append: + for line in extra_append: + afile.write('%s\n' % line) + + if task in ['populate_sdk', 'populate_sdk_ext']: + outputdir = rd.getVar('SDK_DEPLOY', True) + else: + outputdir = rd.getVar('DEPLOY_DIR_IMAGE', True) + + tinfoil.shutdown() + + options = '' + if task: + options += '-c %s' % task + + # run bitbake to build image (or specified task) + try: + exec_build_env_command(config.init_path, basepath, + 'bitbake %s %s' % (options, image), watch=True) + except ExecutionError as err: + return (err.exitcode, None) + finally: + if os.path.isfile(appendfile): + os.unlink(appendfile) + return (0, outputdir) + + +def register_commands(subparsers, context): + """Register devtool subcommands from the build-image plugin""" + 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', + 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 ' + '(separate multiple packages by commas)', + metavar='PACKAGES') + parser.set_defaults(func=build_image) diff --git a/scripts/lib/devtool/build_sdk.py b/scripts/lib/devtool/build_sdk.py new file mode 100644 index 0000000..b89d65b --- /dev/null +++ b/scripts/lib/devtool/build_sdk.py @@ -0,0 +1,65 @@ +# Development tool - build-sdk command plugin +# +# Copyright (C) 2015-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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +import subprocess +import logging +import glob +import shutil +import errno +import sys +import tempfile +from devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError +from devtool import build_image + +logger = logging.getLogger('devtool') + + +def build_sdk(args, config, basepath, workspace): + """Entry point for the devtool build-sdk command""" + + sdk_targets = config.get('SDK', 'sdk_targets', '').split() + if sdk_targets: + image = sdk_targets[0] + else: + raise DevtoolError('Unable to determine image to build SDK for') + + extra_append = ['SDK_DERIVATIVE = "1"'] + try: + result, outputdir = build_image.build_image_task(config, + basepath, + workspace, + image, + task='populate_sdk_ext', + extra_append=extra_append) + except build_image.TargetNotImageError: + raise DevtoolError('Unable to determine image to build SDK for') + + if result == 0: + logger.info('Successfully built SDK. You can find output files in %s' + % outputdir) + return result + + +def register_commands(subparsers, context): + """Register devtool subcommands""" + if context.fixed_setup: + parser_build_sdk = subparsers.add_parser('build-sdk', + help='Build a derivative SDK of this one', + description='Builds an extensible SDK based upon this one and the items in your workspace', + group='advanced') + parser_build_sdk.set_defaults(func=build_sdk) diff --git a/scripts/lib/devtool/deploy.py b/scripts/lib/devtool/deploy.py new file mode 100644 index 0000000..66644cc --- /dev/null +++ b/scripts/lib/devtool/deploy.py @@ -0,0 +1,304 @@ +# Development tool - deploy/undeploy command plugin +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Devtool plugin containing the deploy subcommands""" + +import os +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, 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 + 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') + 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('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') + lines.append(' do') + if dryrun: + lines.append(' if [ ! -d $file ] ; then') + lines.append(' echo $file') + lines.append(' fi') + else: + lines.append(' if [ -d $file ] ; then') + # 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') + lines.append(' done') + 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') + + 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: + 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') + 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: + lines.append('echo "NOTE: Successfully undeployed $1"') + lines.append('done') + + # 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 + import math + import oe.recipeutils + + check_workspace_recipe(workspace, args.recipename, checksrc=False) + + try: + host, destdir = args.target.split(':') + except ValueError: + destdir = '/' + else: + args.target = host + if not destdir.endswith('/'): + destdir += '/' + + tinfoil = setup_tinfoil(basepath=basepath) + try: + rd = oe.recipeutils.parse_recipe_simple(tinfoil.cooker, args.recipename, tinfoil.config_data) + except Exception as e: + raise DevtoolError('Exception parsing recipe %s: %s' % + (args.recipename, e)) + recipe_outdir = rd.getVar('D', True) + if not os.path.exists(recipe_outdir) or not os.listdir(recipe_outdir): + raise DevtoolError('No files to deploy - have you built the %s ' + '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 item, _ in filelist: + print(' %s' % item) + return 0 + + + extraoptions = '' + if args.no_host_check: + extraoptions += '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' + if not args.show_status: + extraoptions += ' -q' + + # 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' + 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) + # 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 ' + '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 %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') + + logger.info('Successfully deployed %s' % recipe_outdir) + + 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)) + + return 0 + +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' + if not args.show_status: + extraoptions += ' -q' + + args.target = args.target.split(':')[0] + + tmpdir = tempfile.mkdtemp(prefix='devtool') + try: + tmpscript = '/tmp/devtool_undeploy.sh' + 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) + # 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') + + if not args.all and not args.dry_run: + logger.info('Successfully undeployed %s' % args.recipename) + return 0 + + +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. 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', + 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 (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) diff --git a/scripts/lib/devtool/package.py b/scripts/lib/devtool/package.py new file mode 100644 index 0000000..afb5809 --- /dev/null +++ b/scripts/lib/devtool/package.py @@ -0,0 +1,62 @@ +# Development tool - package command plugin +# +# Copyright (C) 2014-2015 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Devtool plugin containing the package subcommands""" + +import os +import subprocess +import logging +from bb.process import ExecutionError +from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, DevtoolError + +logger = logging.getLogger('devtool') + +def package(args, config, basepath, workspace): + """Entry point for the devtool 'package' subcommand""" + check_workspace_recipe(workspace, args.recipename) + + tinfoil = setup_tinfoil(basepath=basepath) + try: + tinfoil.prepare(config_only=True) + + image_pkgtype = config.get('Package', 'image_pkgtype', '') + if not image_pkgtype: + image_pkgtype = tinfoil.config_data.getVar('IMAGE_PKGTYPE', True) + + deploy_dir_pkg = tinfoil.config_data.getVar('DEPLOY_DIR_%s' % image_pkgtype.upper(), True) + finally: + tinfoil.shutdown() + + package_task = config.get('Package', 'package_task', 'package_write_%s' % image_pkgtype) + try: + exec_build_env_command(config.init_path, basepath, 'bitbake -c %s %s' % (package_task, args.recipename), watch=True) + except bb.process.ExecutionError as e: + # We've already seen the output since watch=True, so just ensure we return something to the user + return e.exitcode + + logger.info('Your packages are in %s' % deploy_dir_pkg) + + return 0 + +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', + group='testbuild', order=-5) + parser_package.add_argument('recipename', help='Recipe to package') + parser_package.set_defaults(func=package) diff --git a/scripts/lib/devtool/runqemu.py b/scripts/lib/devtool/runqemu.py new file mode 100644 index 0000000..daee7fb --- /dev/null +++ b/scripts/lib/devtool/runqemu.py @@ -0,0 +1,65 @@ +# Development tool - runqemu command plugin +# +# Copyright (C) 2015 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Devtool runqemu plugin""" + +import os +import bb +import logging +import argparse +import glob +from devtool import exec_build_env_command, setup_tinfoil, DevtoolError + +logger = logging.getLogger('devtool') + +def runqemu(args, config, basepath, workspace): + """Entry point for the devtool 'runqemu' subcommand""" + + tinfoil = setup_tinfoil(config_only=True, basepath=basepath) + machine = tinfoil.config_data.getVar('MACHINE', True) + bindir_native = tinfoil.config_data.getVar('STAGING_BINDIR_NATIVE', True) + tinfoil.shutdown() + + if not glob.glob(os.path.join(bindir_native, 'qemu-system-*')): + raise DevtoolError('QEMU is not available within this SDK') + + imagename = args.imagename + if not imagename: + sdk_targets = config.get('SDK', 'sdk_targets', '').split() + if sdk_targets: + imagename = sdk_targets[0] + if not imagename: + raise DevtoolError('Unable to determine image name to run, please specify one') + + try: + exec_build_env_command(config.init_path, basepath, 'runqemu %s %s %s' % (machine, imagename, " ".join(args.args)), watch=True) + except bb.process.ExecutionError as e: + # We've already seen the output since watch=True, so just ensure we return something to the user + return e.exitcode + + return 0 + +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', + 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) + parser_runqemu.set_defaults(func=runqemu) diff --git a/scripts/lib/devtool/sdk.py b/scripts/lib/devtool/sdk.py new file mode 100644 index 0000000..46fd12b --- /dev/null +++ b/scripts/lib/devtool/sdk.py @@ -0,0 +1,366 @@ +# Development tool - sdk-update command plugin +# +# Copyright (C) 2015-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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +import subprocess +import logging +import glob +import shutil +import errno +import sys +import tempfile +import re +from devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError + +logger = logging.getLogger('devtool') + +def parse_locked_sigs(sigfile_path): + """Return : dictionary""" + sig_dict = {} + with open(sigfile_path) as f: + lines = f.readlines() + for line in lines: + if ':' in line: + taskkey, _, hashval = line.rpartition(':') + sig_dict[taskkey.strip()] = hashval.split()[0] + return sig_dict + +def generate_update_dict(sigfile_new, sigfile_old): + """Return a dict containing : which indicates what need to be updated""" + update_dict = {} + sigdict_new = parse_locked_sigs(sigfile_new) + sigdict_old = parse_locked_sigs(sigfile_old) + for k in sigdict_new: + if k not in sigdict_old: + update_dict[k] = sigdict_new[k] + continue + if sigdict_new[k] != sigdict_old[k]: + update_dict[k] = sigdict_new[k] + continue + return update_dict + +def get_sstate_objects(update_dict, sstate_dir): + """Return a list containing sstate objects which are to be installed""" + sstate_objects = [] + for k in update_dict: + files = set() + hashval = update_dict[k] + p = sstate_dir + '/' + hashval[:2] + '/*' + hashval + '*.tgz' + files |= set(glob.glob(p)) + p = sstate_dir + '/*/' + hashval[:2] + '/*' + hashval + '*.tgz' + files |= set(glob.glob(p)) + files = list(files) + if len(files) == 1: + sstate_objects.extend(files) + elif len(files) > 1: + logger.error("More than one matching sstate object found for %s" % hashval) + + return sstate_objects + +def mkdir(d): + try: + os.makedirs(d) + except OSError as e: + if e.errno != errno.EEXIST: + raise e + +def install_sstate_objects(sstate_objects, src_sdk, dest_sdk): + """Install sstate objects into destination SDK""" + sstate_dir = os.path.join(dest_sdk, 'sstate-cache') + if not os.path.exists(sstate_dir): + logger.error("Missing sstate-cache directory in %s, it might not be an extensible SDK." % dest_sdk) + raise + for sb in sstate_objects: + dst = sb.replace(src_sdk, dest_sdk) + destdir = os.path.dirname(dst) + mkdir(destdir) + logger.debug("Copying %s to %s" % (sb, dst)) + shutil.copy(sb, dst) + +def check_manifest(fn, basepath): + import bb.utils + changedfiles = [] + with open(fn, 'r') as f: + for line in f: + splitline = line.split() + if len(splitline) > 1: + chksum = splitline[0] + fpath = splitline[1] + curr_chksum = bb.utils.sha256_file(os.path.join(basepath, fpath)) + if chksum != curr_chksum: + logger.debug('File %s changed: old csum = %s, new = %s' % (os.path.join(basepath, fpath), curr_chksum, chksum)) + changedfiles.append(fpath) + return changedfiles + +def sdk_update(args, config, basepath, workspace): + # Fetch locked-sigs.inc file from remote/local destination + updateserver = args.updateserver + if not updateserver: + updateserver = config.get('SDK', 'updateserver', '') + logger.debug("updateserver: %s" % updateserver) + + # Make sure we are using sdk-update from within SDK + logger.debug("basepath = %s" % basepath) + old_locked_sig_file_path = os.path.join(basepath, 'conf/locked-sigs.inc') + if not os.path.exists(old_locked_sig_file_path): + logger.error("Not using devtool's sdk-update command from within an extensible SDK. Please specify correct basepath via --basepath option") + return -1 + else: + logger.debug("Found conf/locked-sigs.inc in %s" % basepath) + + if ':' in updateserver: + is_remote = True + else: + is_remote = False + + layers_dir = os.path.join(basepath, 'layers') + conf_dir = os.path.join(basepath, 'conf') + + # Grab variable values + tinfoil = setup_tinfoil(config_only=True, basepath=basepath) + try: + stamps_dir = tinfoil.config_data.getVar('STAMPS_DIR', True) + sstate_mirrors = tinfoil.config_data.getVar('SSTATE_MIRRORS', True) + site_conf_version = tinfoil.config_data.getVar('SITE_CONF_VERSION', True) + finally: + tinfoil.shutdown() + + if not is_remote: + # devtool sdk-update /local/path/to/latest/sdk + new_locked_sig_file_path = os.path.join(updateserver, 'conf/locked-sigs.inc') + if not os.path.exists(new_locked_sig_file_path): + logger.error("%s doesn't exist or is not an extensible SDK" % updateserver) + return -1 + else: + logger.debug("Found conf/locked-sigs.inc in %s" % updateserver) + update_dict = generate_update_dict(new_locked_sig_file_path, old_locked_sig_file_path) + logger.debug("update_dict = %s" % update_dict) + newsdk_path = updateserver + sstate_dir = os.path.join(newsdk_path, 'sstate-cache') + if not os.path.exists(sstate_dir): + logger.error("sstate-cache directory not found under %s" % newsdk_path) + return 1 + sstate_objects = get_sstate_objects(update_dict, sstate_dir) + logger.debug("sstate_objects = %s" % sstate_objects) + if len(sstate_objects) == 0: + logger.info("No need to update.") + return 0 + logger.info("Installing sstate objects into %s", basepath) + install_sstate_objects(sstate_objects, updateserver.rstrip('/'), basepath) + logger.info("Updating configuration files") + new_conf_dir = os.path.join(updateserver, 'conf') + shutil.rmtree(conf_dir) + shutil.copytree(new_conf_dir, conf_dir) + logger.info("Updating layers") + new_layers_dir = os.path.join(updateserver, 'layers') + shutil.rmtree(layers_dir) + ret = subprocess.call("cp -a %s %s" % (new_layers_dir, layers_dir), shell=True) + if ret != 0: + logger.error("Copying %s to %s failed" % (new_layers_dir, layers_dir)) + return ret + else: + # devtool sdk-update http://myhost/sdk + tmpsdk_dir = tempfile.mkdtemp() + try: + os.makedirs(os.path.join(tmpsdk_dir, 'conf')) + new_locked_sig_file_path = os.path.join(tmpsdk_dir, 'conf', 'locked-sigs.inc') + # Fetch manifest from server + tmpmanifest = os.path.join(tmpsdk_dir, 'conf', 'sdk-conf-manifest') + ret = subprocess.call("wget -q -O %s %s/conf/sdk-conf-manifest" % (tmpmanifest, updateserver), shell=True) + changedfiles = check_manifest(tmpmanifest, basepath) + if not changedfiles: + logger.info("Already up-to-date") + return 0 + # Update metadata + logger.debug("Updating metadata via git ...") + #Check for the status before doing a fetch and reset + if os.path.exists(os.path.join(basepath, 'layers/.git')): + out = subprocess.check_output("git status --porcelain", shell=True, cwd=layers_dir) + if not out: + ret = subprocess.call("git fetch --all; git reset --hard", shell=True, cwd=layers_dir) + else: + logger.error("Failed to update metadata as there have been changes made to it. Aborting."); + logger.error("Changed files:\n%s" % out); + return -1 + else: + ret = -1 + if ret != 0: + ret = subprocess.call("git clone %s/layers/.git" % updateserver, shell=True, cwd=tmpsdk_dir) + if ret != 0: + logger.error("Updating metadata via git failed") + return ret + logger.debug("Updating conf files ...") + for changedfile in changedfiles: + ret = subprocess.call("wget -q -O %s %s/%s" % (changedfile, updateserver, changedfile), shell=True, cwd=tmpsdk_dir) + if ret != 0: + logger.error("Updating %s failed" % changedfile) + return ret + + # Check if UNINATIVE_CHECKSUM changed + uninative = False + if 'conf/local.conf' in changedfiles: + def read_uninative_checksums(fn): + chksumitems = [] + with open(fn, 'r') as f: + for line in f: + if line.startswith('UNINATIVE_CHECKSUM'): + splitline = re.split(r'[\[\]"\']', line) + if len(splitline) > 3: + chksumitems.append((splitline[1], splitline[3])) + return chksumitems + + oldsums = read_uninative_checksums(os.path.join(basepath, 'conf/local.conf')) + newsums = read_uninative_checksums(os.path.join(tmpsdk_dir, 'conf/local.conf')) + if oldsums != newsums: + uninative = True + for buildarch, chksum in newsums: + uninative_file = os.path.join('downloads', 'uninative', chksum, '%s-nativesdk-libc.tar.bz2' % buildarch) + mkdir(os.path.join(tmpsdk_dir, os.path.dirname(uninative_file))) + ret = subprocess.call("wget -q -O %s %s/%s" % (uninative_file, updateserver, uninative_file), shell=True, cwd=tmpsdk_dir) + + # Ok, all is well at this point - move everything over + tmplayers_dir = os.path.join(tmpsdk_dir, 'layers') + if os.path.exists(tmplayers_dir): + shutil.rmtree(layers_dir) + shutil.move(tmplayers_dir, layers_dir) + for changedfile in changedfiles: + destfile = os.path.join(basepath, changedfile) + os.remove(destfile) + shutil.move(os.path.join(tmpsdk_dir, changedfile), destfile) + os.remove(os.path.join(conf_dir, 'sdk-conf-manifest')) + shutil.move(tmpmanifest, conf_dir) + if uninative: + shutil.rmtree(os.path.join(basepath, 'downloads', 'uninative')) + shutil.move(os.path.join(tmpsdk_dir, 'downloads', 'uninative'), os.path.join(basepath, 'downloads')) + + if not sstate_mirrors: + with open(os.path.join(conf_dir, 'site.conf'), 'a') as f: + f.write('SCONF_VERSION = "%s"\n' % site_conf_version) + f.write('SSTATE_MIRRORS_append = " file://.* %s/sstate-cache/PATH \\n "\n' % updateserver) + finally: + shutil.rmtree(tmpsdk_dir) + + if not args.skip_prepare: + # Find all potentially updateable tasks + sdk_update_targets = [] + tasks = ['do_populate_sysroot', 'do_packagedata'] + for root, _, files in os.walk(stamps_dir): + for fn in files: + if not '.sigdata.' in fn: + for task in tasks: + if '.%s.' % task in fn or '.%s_setscene.' % task in fn: + sdk_update_targets.append('%s:%s' % (os.path.basename(root), task)) + # Run bitbake command for the whole SDK + logger.info("Preparing build system... (This may take some time.)") + try: + exec_build_env_command(config.init_path, basepath, 'bitbake --setscene-only %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT) + output, _ = exec_build_env_command(config.init_path, basepath, 'bitbake -n %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT) + runlines = [] + for line in output.splitlines(): + if 'Running task ' in line: + runlines.append(line) + if runlines: + logger.error('Unexecuted tasks found in preparation log:\n %s' % '\n '.join(runlines)) + return -1 + except bb.process.ExecutionError as e: + logger.error('Preparation failed:\n%s' % e.stdout) + return -1 + return 0 + +def sdk_install(args, config, basepath, workspace): + """Entry point for the devtool sdk-install command""" + + import oe.recipeutils + import bb.process + + for recipe in args.recipename: + if recipe in workspace: + raise DevtoolError('recipe %s is a recipe in your workspace' % recipe) + + tasks = ['do_populate_sysroot', 'do_packagedata'] + stampprefixes = {} + def checkstamp(recipe): + stampprefix = stampprefixes[recipe] + stamps = glob.glob(stampprefix + '*') + for stamp in stamps: + if '.sigdata.' not in stamp and stamp.startswith((stampprefix + '.', stampprefix + '_setscene.')): + return True + else: + return False + + install_recipes = [] + tinfoil = setup_tinfoil(config_only=False, basepath=basepath) + try: + for recipe in args.recipename: + rd = parse_recipe(config, tinfoil, recipe, True) + if not rd: + return 1 + stampprefixes[recipe] = '%s.%s' % (rd.getVar('STAMP', True), tasks[0]) + if checkstamp(recipe): + logger.info('%s is already installed' % recipe) + else: + install_recipes.append(recipe) + finally: + tinfoil.shutdown() + + if install_recipes: + logger.info('Installing %s...' % ', '.join(install_recipes)) + install_tasks = [] + for recipe in install_recipes: + for task in tasks: + if recipe.endswith('-native') and 'package' in task: + continue + install_tasks.append('%s:%s' % (recipe, task)) + options = '' + if not args.allow_build: + options += ' --setscene-only' + try: + exec_build_env_command(config.init_path, basepath, 'bitbake %s %s' % (options, ' '.join(install_tasks)), watch=True) + except bb.process.ExecutionError as e: + raise DevtoolError('Failed to install %s:\n%s' % (recipe, str(e))) + failed = False + for recipe in install_recipes: + if checkstamp(recipe): + logger.info('Successfully installed %s' % recipe) + else: + raise DevtoolError('Failed to install %s - unavailable' % recipe) + failed = True + if failed: + return 2 + +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', + description='Updates installed SDK components from a remote server', + group='sdk') + 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) + + 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.add_argument('-s', '--allow-build', help='Allow building requested item(s) from source', action='store_true') + parser_sdk_install.set_defaults(func=sdk_install) diff --git a/scripts/lib/devtool/search.py b/scripts/lib/devtool/search.py new file mode 100644 index 0000000..b44bed7 --- /dev/null +++ b/scripts/lib/devtool/search.py @@ -0,0 +1,88 @@ +# Development tool - search command plugin +# +# Copyright (C) 2015 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Devtool search plugin""" + +import os +import bb +import logging +import argparse +import re +from devtool import setup_tinfoil, parse_recipe, DevtoolError + +logger = logging.getLogger('devtool') + +def search(args, config, basepath, workspace): + """Entry point for the devtool 'search' subcommand""" + + tinfoil = setup_tinfoil(config_only=False, basepath=basepath) + try: + pkgdata_dir = tinfoil.config_data.getVar('PKGDATA_DIR', True) + defsummary = tinfoil.config_data.getVar('