Skip to contentSkip to conversation

Extensible SDK improvements redux

PATCH New Paul Eggleton 1 participant 15 messages Jan 22, 2016

lore
Share & tools
lore
#0 lore raw

cover Extensible SDK improvements redux

* Fixes around ensuring pkgdata is present to help find dependencies
* Fixes for the SDK update process
* Add the ability to install prebuilt items into the SDK (from sstate)
* Fix a few other issues I noticed in the process

Note that the world pkgdata implementation relies on a BitBake patch I just sent.

The following changes since commit 8084c185c43cb7e7f5e29d31939dfd308f8a0f93:

udev: Add 2 patches to support 4.4 kernel (2016-01-20 09:20:06 +0000)

are available in the git repository at:

git://git.openembedded.org/openembedded-core-contrib paule/extsdkfixes5-oe
http://cgit.openembedded.org/cgit.cgi/openembedded-core-contrib/log/?h=paule/extsdkfixes5-oe

Paul Eggleton (14):
classes/populate_sdk_ext: add option to bring in pkgdata for world
devtool: build: ensure pkgdata is written out
devtool: sdk-update: improve temp directory handling
devtool: sdk-update: improve SDK update process robustness
devtool: add sdk-install subcommand classes/populate_sdk_ext: drop ext-sdk-prepare.py when installing classes/populate_sdk*: add dependencies on script files toolchain-shar-extract.sh: improve behaviour when xz is not installed
gen-lockedsig-cache: copy correct native sstate into ext SDK
recipetool: create: strip quotes from values extracted from
CMakeLists.txt
devtool / recipetool: support specifying a subdirectory within the
fetched source
recipetool: create: extract SRC_URI from local git repositories
recipetool: create: fix extraction of name from URLs ending in /
recipetool: create: better fix for fetch error handling

 meta/classes/populate_sdk_base.bbclass       |   3 +
 meta/classes/populate_sdk_ext.bbclass        |  46 +++++-
 meta/files/toolchain-shar-extract.sh         |   7 +-
 meta/lib/oe/copy_buildsystem.py              |  61 +++++++-
 meta/lib/oeqa/selftest/devtool.py            |   7 +-
 meta/recipes-core/meta/meta-world-pkgdata.bb |  50 +++++++
 scripts/gen-lockedsig-cache                  |   6 +-
 scripts/lib/devtool/build.py                 |  16 +-
 scripts/lib/devtool/sdk.py                   | 212 ++++++++++++++++++++-------
 scripts/lib/devtool/standard.py              |   6 +
 scripts/lib/recipetool/create.py             |  47 ++++--
 scripts/lib/recipetool/create_buildsys.py    |  13 +-
 12 files changed, 392 insertions(+), 82 deletions(-)
 create mode 100644 meta/recipes-core/meta/meta-world-pkgdata.bb
Signature · 1 line
2.5.0
#1 lore raw

PATCH 1/14 classes/populate_sdk_ext: add option to bring in pkgdata for world

Add a variable SDK_INCLUDE_PKGDATA which you can set to "1" to include pkgdata for all recipes in the world target. There are a couple of uses for this:

1) If you use "devtool add" to add a recipe that builds something which depends on anything in world, the dependency can then be correctly mapped to the recipe providing it and that recipe can be added to DEPENDS, since we have the pkg-config and shared library dependency data within pkgdata.
2) You'll be able to search for these recipes and any files they package for the target with "devtool search" since that also uses pkgdata

This of course assumes you've tailored world through EXCLUDE_FROM_WORLD to only include recipes you'd want built in your distro, but I think that's a reasonable assumption; failing that there is a WORLD_PKGDATA_EXCLUDE variable that you can set to exclude any recipes you don't want.

Note that this patch relies on functionality implemented in a recent BitBake patch and will not work without it.

Implements [YOCTO #8600].

---
 meta/classes/populate_sdk_ext.bbclass        | 24 +++++++++++-
 meta/lib/oe/copy_buildsystem.py              | 58 +++++++++++++++++++++++++++-
 meta/recipes-core/meta/meta-world-pkgdata.bb | 50 ++++++++++++++++++++++++
 scripts/lib/devtool/sdk.py                   |  6 +--
 4 files changed, 131 insertions(+), 7 deletions(-)
 create mode 100644 meta/recipes-core/meta/meta-world-pkgdata.bb
meta/classes/populate_sdk_ext.bbclass +22 -2
index e5e55b3..ebb57bb 100644
@@ -24,6 +24,7 @@ SDK_INHERIT_BLACKLIST ?= "buildhistory icecc"
 SDK_UPDATE_URL ?= ""
 
 SDK_TARGETS ?= "${PN}"
+SDK_INSTALL_TARGETS = "${SDK_TARGETS} ${@'meta-world-pkgdata:do_allpackagedata' if d.getVar('SDK_INCLUDE_PKGDATA', True) == '1' else ''}"
 OE_INIT_ENV_SCRIPT ?= "oe-init-build-env"
 
 # The files from COREBASE that you want preserved in the COREBASE copied
@@ -45,6 +46,7 @@ SDK_TITLE_task-populate-sdk-ext = "${@d.getVar('DISTRO_NAME', True) or d.getVar(
 
 python copy_buildsystem () {
     import re
+    import shutil
     import oe.copy_buildsystem
 
     oe_init_env_script = d.getVar('OE_INIT_ENV_SCRIPT', True)
@@ -91,6 +93,7 @@ python copy_buildsystem () {
     config.set('General', 'core_meta_subdir', core_meta_subdir)
     config.add_section('SDK')
     config.set('SDK', 'sdk_targets', d.getVar('SDK_TARGETS', True))
+    config.set('SDK', 'sdk_update_targets', d.getVar('SDK_INSTALL_TARGETS', True))
     updateurl = d.getVar('SDK_UPDATE_URL', True)
     if updateurl:
         config.set('SDK', 'updateserver', updateurl)
@@ -199,6 +202,22 @@ python copy_buildsystem () {
                                                    d.getVar('SSTATE_DIR', True),
                                                    sstate_out, d,
                                                    fixedlsbstring)
+
+    # Add packagedata if enabled
+    if d.getVar('SDK_INCLUDE_PKGDATA', True) == '1':
+        lockedsigs_base = d.getVar('WORKDIR', True) + '/locked-sigs-base.inc'
+        lockedsigs_copy = d.getVar('WORKDIR', True) + '/locked-sigs-copy.inc'
+        shutil.move(lockedsigs_pruned, lockedsigs_base)
+        oe.copy_buildsystem.merge_lockedsigs(['do_packagedata'],
+                                             lockedsigs_base,
+                                             d.getVar('STAGING_DIR_HOST', True) + '/world-pkgdata/locked-sigs-pkgdata.inc',
+                                             lockedsigs_pruned,
+                                             lockedsigs_copy)
+        oe.copy_buildsystem.create_locked_sstate_cache(lockedsigs_copy,
+                                                       d.getVar('SSTATE_DIR', True),
+                                                       sstate_out, d,
+                                                       fixedlsbstring)
+
     # We don't need sstate do_package files
     for root, dirs, files in os.walk(sstate_out):
         for name in files:
@@ -268,7 +287,7 @@ sdk_ext_postinst() {
 		# current working directory when first ran, nor will it set $1 when
 		# sourcing a script. That is why this has to look so ugly.
 		LOGFILE="$target_sdk_dir/preparing_build_system.log"
-		sh -c ". buildtools/environment-setup* > $LOGFILE && cd $target_sdk_dir/`dirname ${oe_init_build_env_path}` && set $target_sdk_dir && . $target_sdk_dir/${oe_init_build_env_path} $target_sdk_dir >> $LOGFILE && python $target_sdk_dir/ext-sdk-prepare.py '${SDK_TARGETS}' >> $LOGFILE 2>&1" || { echo "ERROR: SDK preparation failed: see $LOGFILE"; echo "printf 'ERROR: this SDK was not fully installed and needs reinstalling\n'" >> $env_setup_script ; exit 1 ; }
+		sh -c ". buildtools/environment-setup* > $LOGFILE && cd $target_sdk_dir/`dirname ${oe_init_build_env_path}` && set $target_sdk_dir && . $target_sdk_dir/${oe_init_build_env_path} $target_sdk_dir >> $LOGFILE && python $target_sdk_dir/ext-sdk-prepare.py '${SDK_INSTALL_TARGETS}' >> $LOGFILE 2>&1" || { echo "ERROR: SDK preparation failed: see $LOGFILE"; echo "printf 'ERROR: this SDK was not fully installed and needs reinstalling\n'" >> $env_setup_script ; exit 1 ; }
 	fi
 	echo done
 }
@@ -314,7 +333,8 @@ def get_sdk_ext_rdepends(d):
 do_populate_sdk_ext[dirs] = "${@d.getVarFlag('do_populate_sdk', 'dirs', False)}"
 
 do_populate_sdk_ext[depends] = "${@d.getVarFlag('do_populate_sdk', 'depends', False)} \
-                                buildtools-tarball:do_populate_sdk uninative-tarball:do_populate_sdk"
+                                buildtools-tarball:do_populate_sdk uninative-tarball:do_populate_sdk \
+                                ${@'meta-world-pkgdata:do_collect_packagedata' if d.getVar('SDK_INCLUDE_PKGDATA', True) == '1' else ''}"
 
 do_populate_sdk_ext[rdepends] += "${@' '.join([x + ':do_build' for x in d.getVar('SDK_TARGETS', True).split()])}"
 
meta/lib/oe/copy_buildsystem.py +56 -2

66 lines not on this page · raw message

meta/recipes-core/meta/meta-world-pkgdata.bb new file +50 -0

50 lines not on this page · raw message

scripts/lib/devtool/sdk.py +3 -3
index 0872df6..68139aa 100644
@@ -175,12 +175,12 @@ def sdk_update(args, config, basepath, workspace):
 
     if not args.skip_prepare:
         # Run bitbake command for the whole SDK
-        sdk_targets = config.get('SDK', 'sdk_targets')
+        sdk_update_targets = config.get('SDK', 'sdk_update_targets', config.get('SDK', 'sdk_targets'))
         logger.info("Preparing build system... (This may take some time.)")
         try:
-            exec_build_env_command(config.init_path, basepath, 'bitbake %s --setscene-only' % sdk_targets)
+            exec_build_env_command(config.init_path, basepath, 'bitbake %s --setscene-only' % sdk_update_targets)
         except:
-            logger.error('bitbake %s failed' % sdk_targets)
+            logger.error('bitbake %s failed' % sdk_update_targets)
             return -1
     return 0
 
Signature · 1 line
2.5.0
#2 lore raw

PATCH 2/14 devtool: build: ensure pkgdata is written out

In reply to Paul Eggleton

When you run devtool build, you need to have the pkgdata written out at the end, so that if what you're adding is a library and the next thing you add is something that depends on that library, the necessary information to map the dependency back to the recipe is present. In practical terms all this means is we need do_packagedata to run in addition to do_populate_sysroot.

This does mean that do_package needs to run which wasn't running before, and that means that the few package QA tests that run within do_package such as installed-vs-shipped will now be run. This may be a bit bothersome, and prompted a fix for one of our oe-selftest tests as a result, but I don't see an easy way around it. Ultimately if you care about using the recipe in an image you'll need to fix any such errors anyway.

Fixes [YOCTO #8887].

---
 meta/lib/oeqa/selftest/devtool.py |  7 ++++++-
 scripts/lib/devtool/build.py      | 16 +++++++++++-----
 2 files changed, 17 insertions(+), 6 deletions(-)
meta/lib/oeqa/selftest/devtool.py +6 -1
index 43c7cda..d95cb08 100644
@@ -226,8 +226,13 @@ class DevtoolTests(DevtoolBase):
         # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then)
         bitbake('libftdi -c cleansstate')
         # libftdi's python/CMakeLists.txt is a bit broken, so let's just disable it
+        # There's also the matter of it installing cmake files to a path we don't
+        # normally cover, which triggers the installed-vs-shipped QA test we have
+        # within do_package
         recipefile = '%s/recipes/libftdi/libftdi_%s.bb' % (self.workspacedir, version)
-        result = runCmd('recipetool setvar %s EXTRA_OECMAKE -- "-DPYTHON_BINDINGS=OFF"' % recipefile)
+        result = runCmd('recipetool setvar %s EXTRA_OECMAKE -- \'-DPYTHON_BINDINGS=OFF -DLIBFTDI_CMAKE_CONFIG_DIR=${datadir}/cmake/Modules\'' % recipefile)
+        with open(recipefile, 'a') as f:
+            f.write('\nFILES_${PN}-dev += "${datadir}/cmake/Modules"\n')
         # Test devtool build
         result = runCmd('devtool build libftdi')
         staging_libdir = get_bb_var('STAGING_LIBDIR', 'libftdi')
scripts/lib/devtool/build.py +11 -5
index c4c0c9f..b10a6a9 100644
@@ -46,21 +46,27 @@ def _set_file_values(fn, values):
             f.writelines(newlines)
     return updated
 
-def _get_build_task(config):
-    return config.get('Build', 'build_task', 'populate_sysroot')
+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_task = _get_build_task(config)
+    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:
-        exec_build_env_command(config.init_path, basepath, 'bitbake -c %s %s' % (build_task, args.recipename), watch=True)
+        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
@@ -73,7 +79,7 @@ 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 do_%s)' % _get_build_task(context.config))
+                                         description='Builds the specified recipe using bitbake (up to and including %s)' % ', '.join(_get_build_tasks(context.config)))
     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)
Signature · 1 line
2.5.0
#3 lore raw

PATCH 3/14 devtool: sdk-update: improve temp directory handling

In reply to Paul Eggleton

* Use tempfile.mkdtemp() instead of hardcoding temp dir
* Set a variable early for the temp locked sigs file and use that everywhere
* Delete the temp dir at the end

---
 scripts/lib/devtool/sdk.py | 75 +++++++++++++++++++++++-----------------------
 1 file changed, 38 insertions(+), 37 deletions(-)
scripts/lib/devtool/sdk.py +38 -37
index 68139aa..4fcd36a 100644
@@ -7,6 +7,7 @@ import glob
 import shutil
 import errno
 import sys
+import tempfile
 from devtool import exec_build_env_command, setup_tinfoil, DevtoolError
 
 logger = logging.getLogger('devtool')
@@ -133,45 +134,45 @@ def sdk_update(args, config, basepath, workspace):
             return ret
     else:
         # devtool sdk-update http://myhost/sdk
-        tmpsdk_dir = '/tmp/sdk-ext'
-        if os.path.exists(tmpsdk_dir):
-            shutil.rmtree(tmpsdk_dir)
-        os.makedirs(tmpsdk_dir)
-        os.makedirs(os.path.join(tmpsdk_dir, 'conf'))
-        # Fetch locked-sigs.inc from update server
-        ret = subprocess.call("wget -q -O - %s/conf/locked-sigs.inc > %s/locked-sigs.inc" % (updateserver, os.path.join(tmpsdk_dir, 'conf')), shell=True)
-        if ret != 0:
-            logger.error("Fetching conf/locked-sigs.inc from %s to %s/locked-sigs.inc failed" % (updateserver, os.path.join(tmpsdk_dir, 'conf')))
-            return ret
-        else:
-            logger.info("Fetching conf/locked-sigs.inc from %s to %s/locked-sigs.inc succeeded" % (updateserver, os.path.join(tmpsdk_dir, 'conf')))
-        new_locked_sig_file_path = os.path.join(tmpsdk_dir, 'conf/locked-sigs.inc')
-        update_dict = generate_update_dict(new_locked_sig_file_path, old_locked_sig_file_path)
-        logger.debug("update_dict = %s" % update_dict)
-        if len(update_dict) == 0:
-            logger.info("No need to update.")
-            return 0
-        # Update metadata
-        logger.debug("Updating meta data via git ...")
-        # Try using 'git pull', if failed, use 'git clone'
-        if os.path.exists(os.path.join(basepath, 'layers/.git')):
-            ret = subprocess.call("cd layers && git pull %s/layers/.git" % updateserver, shell=True)
-        else:
-            ret = -1
-        if ret != 0:
-            ret = subprocess.call("rm -rf layers && git clone %s/layers/.git" % updateserver, shell=True)
-        if ret != 0:
-            logger.error("Updating meta data via git failed")
-            return ret
-        logger.debug("Updating conf files ...")
-        conf_files = ['local.conf', 'bblayers.conf', 'devtool.conf', 'locked-sigs.inc']
-        for conf in conf_files:
-            ret = subprocess.call("wget -q -O - %s/conf/%s > conf/%s" % (updateserver, conf, conf), shell=True)
+        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 locked-sigs.inc from update server
+            ret = subprocess.call("wget -q -O - %s/conf/locked-sigs.inc > %s" % (updateserver, new_locked_sig_file_path), shell=True)
             if ret != 0:
-                logger.error("Update %s failed" % conf)
+                logger.error("Fetching conf/locked-sigs.inc from %s to %s failed" % (updateserver, new_locked_sig_file_path))
                 return ret
-        with open(os.path.join(basepath, 'conf/local.conf'), 'a') as f:
-            f.write('SSTATE_MIRRORS_append = " file://.* %s/sstate-cache/PATH \\n "\n' % updateserver)
+            else:
+                logger.info("Fetching conf/locked-sigs.inc from %s to %s succeeded" % (updateserver, new_locked_sig_file_path))
+            update_dict = generate_update_dict(new_locked_sig_file_path, old_locked_sig_file_path)
+            logger.debug("update_dict = %s" % update_dict)
+            if len(update_dict) == 0:
+                logger.info("No need to update.")
+                return 0
+            # Update metadata
+            logger.debug("Updating meta data via git ...")
+            # Try using 'git pull', if failed, use 'git clone'
+            if os.path.exists(os.path.join(basepath, 'layers/.git')):
+                ret = subprocess.call("cd layers && git pull %s/layers/.git" % updateserver, shell=True)
+            else:
+                ret = -1
+            if ret != 0:
+                ret = subprocess.call("rm -rf layers && git clone %s/layers/.git" % updateserver, shell=True)
+            if ret != 0:
+                logger.error("Updating meta data via git failed")
+                return ret
+            logger.debug("Updating conf files ...")
+            conf_files = ['local.conf', 'bblayers.conf', 'devtool.conf', 'locked-sigs.inc']
+            for conf in conf_files:
+                ret = subprocess.call("wget -q -O - %s/conf/%s > conf/%s" % (updateserver, conf, conf), shell=True)
+                if ret != 0:
+                    logger.error("Update %s failed" % conf)
+                    return ret
+            with open(os.path.join(basepath, 'conf/local.conf'), 'a') as f:
+                f.write('SSTATE_MIRRORS_append = " file://.* %s/sstate-cache/PATH \\n "\n' % updateserver)
+        finally:
+            shutil.rmtree(tmpsdk_dir)
 
     if not args.skip_prepare:
         # Run bitbake command for the whole SDK
Signature · 1 line
2.5.0
#4 lore raw

PATCH 4/14 devtool: sdk-update: improve SDK update process robustness

In reply to Paul Eggleton

Make the following improvements to the SDK update process:

* Use a manifest file with sha256sums to track files other than sstate and metadata that we need to update - e.g. conf files. This allows us to handle where files such as auto.conf may or may not be present, as well as the configuration changing without affecting task signatures
- we still want the config files copied in that case rather than it saying nothing needs to be done.
* Write the SSTATE_MIRRORS_append to site.conf rather than local.conf so that local.conf remains static (since we don't want to trigger an update every time). Also, If there is an SSTATE_MIRRORS value already set in the configuration we can skip this and assume it contains the needed packages.
* Allow the update process to be run in any directory, don't assume we're already at the base of the SDK
* Where practical, fetch remote files into a temporary location and then move them to the desired location at the end, to avoid a failed update leaving the SDK in a broken state.
* Update all installed do_populate_sysroot / do_packagedata tasks instead of using the SDK targets. This ensures any item installed through dependencies after installation (e.g. when running "devtool build") won't go stale.

---
 meta/classes/populate_sdk_ext.bbclass |  16 +++++
 scripts/lib/devtool/sdk.py            | 118 ++++++++++++++++++++++++----------
 2 files changed, 100 insertions(+), 34 deletions(-)
meta/classes/populate_sdk_ext.bbclass +16 -0
index ebb57bb..bb8831b 100644
@@ -47,6 +47,7 @@ SDK_TITLE_task-populate-sdk-ext = "${@d.getVar('DISTRO_NAME', True) or d.getVar(
 python copy_buildsystem () {
     import re
     import shutil
+    import glob
     import oe.copy_buildsystem
 
     oe_init_env_script = d.getVar('OE_INIT_ENV_SCRIPT', True)
@@ -224,6 +225,21 @@ python copy_buildsystem () {
             if name.endswith("_package.tgz"):
                 f = os.path.join(root, name)
                 os.remove(f)
+
+    # Write manifest file
+    # Note: at the moment we cannot include the env setup script here to keep
+    # it updated, since it gets modified during SDK installation (see
+    # sdk_ext_postinst() below) thus the checksum we take here would always
+    # be different.
+    manifest_file_list = ['conf/*']
+    manifest_file = os.path.join(baseoutpath, 'conf', 'sdk-conf-manifest')
+    with open(manifest_file, 'w') as f:
+        for item in manifest_file_list:
+            for fn in glob.glob(os.path.join(baseoutpath, item)):
+                if fn == manifest_file:
+                    continue
+                chksum = bb.utils.sha256_file(fn)
+                f.write('%s\t%s\n' % (chksum, os.path.relpath(fn, baseoutpath)))
 }
 
 def extsdk_get_buildtools_filename(d):
scripts/lib/devtool/sdk.py +84 -34

162 lines not on this page · raw message

Signature · 1 line
2.5.0
#5 lore raw

PATCH 5/14 devtool: add sdk-install subcommand

In reply to Paul Eggleton

Add the ability to install additional pre-built items (from shared state) into the extensible SDK. This can already be done implicitly by adding something to DEPENDS within a recipe you're working on and then running "devtool build", but it's useful to be able to explicitly install things particularly if you're using the extensible SDK as a traditional toolchain.

Note that for this command to be useful you need to have SSTATE_MIRRORS set in your SDK configuration, and that mirror needs to be populated with sstate artifacts for recipes you wish to be able to install.

---
 scripts/lib/devtool/sdk.py | 63 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)
scripts/lib/devtool/sdk.py +62 -1
index 80ea871..12de942 100644
@@ -8,7 +8,7 @@ import shutil
 import errno
 import sys
 import tempfile
-from devtool import exec_build_env_command, setup_tinfoil, DevtoolError
+from devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError
 
 logger = logging.getLogger('devtool')
 
@@ -235,6 +235,64 @@ def sdk_update(args, config, basepath, workspace):
             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))
+        try:
+            exec_build_env_command(config.init_path, basepath, 'bitbake --setscene-only %s' % ' '.join(install_tasks))
+        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:
@@ -242,3 +300,6 @@ def register_commands(subparsers, context):
         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.add_argument('recipename', help='Name of the recipe to install the development artifacts for', nargs='+')
+        parser_sdk_install.set_defaults(func=sdk_install)
Signature · 1 line
2.5.0
#6 lore raw

PATCH 6/14 classes/populate_sdk_ext: drop ext-sdk-prepare.py when installing

In reply to Paul Eggleton

At the end of the extensible SDK installation, if we've successfully prepared the build system then we don't need ext-sdk-prepare.py. I had thought earlier that this would be used when updating, but a different mechanism was needed there so this script isn't used for that.

---
 meta/classes/populate_sdk_ext.bbclass | 1 +
 1 file changed, 1 insertion(+)
meta/classes/populate_sdk_ext.bbclass +1 -0
index bb8831b..2944576 100644
@@ -304,6 +304,7 @@ sdk_ext_postinst() {
 		# sourcing a script. That is why this has to look so ugly.
 		LOGFILE="$target_sdk_dir/preparing_build_system.log"
 		sh -c ". buildtools/environment-setup* > $LOGFILE && cd $target_sdk_dir/`dirname ${oe_init_build_env_path}` && set $target_sdk_dir && . $target_sdk_dir/${oe_init_build_env_path} $target_sdk_dir >> $LOGFILE && python $target_sdk_dir/ext-sdk-prepare.py '${SDK_INSTALL_TARGETS}' >> $LOGFILE 2>&1" || { echo "ERROR: SDK preparation failed: see $LOGFILE"; echo "printf 'ERROR: this SDK was not fully installed and needs reinstalling\n'" >> $env_setup_script ; exit 1 ; }
+		rm $target_sdk_dir/ext-sdk-prepare.py
 	fi
 	echo done
 }
Signature · 1 line
2.5.0
#7 lore raw

PATCH 7/14 classes/populate_sdk*: add dependencies on script files

In reply to Paul Eggleton

If the script files we use to construct the SDK installer change then that really ought to trigger re-execution of the do_populate_sdk(_ext) task, so add file-checksums varflags to ensure that happens.

---
 meta/classes/populate_sdk_base.bbclass | 3 +++
 meta/classes/populate_sdk_ext.bbclass  | 5 +++++
 2 files changed, 8 insertions(+)
meta/classes/populate_sdk_base.bbclass +3 -0
index 99b64f7..966161f 100644
@@ -243,6 +243,9 @@ populate_sdk_log_check() {
 	done
 }
 
+do_populate_sdk[file-checksums] += "${COREBASE}/meta/files/toolchain-shar-relocate.sh:True \
+                                    ${COREBASE}/meta/files/toolchain-shar-extract.sh:True"
+
 do_populate_sdk[dirs] = "${PKGDATA_DIR} ${TOPDIR}"
 do_populate_sdk[depends] += "${@' '.join([x + ':do_populate_sysroot' for x in d.getVar('SDK_DEPENDS', True).split()])}  ${@d.getVarFlag('do_rootfs', 'depends', False)}"
 do_populate_sdk[rdepends] = "${@' '.join([x + ':do_populate_sysroot' for x in d.getVar('SDK_RDEPENDS', True).split()])}"
meta/classes/populate_sdk_ext.bbclass +5 -0
index 2944576..a115127 100644
@@ -259,6 +259,7 @@ install_tools() {
 
 	install -m 0644 ${COREBASE}/meta/files/ext-sdk-prepare.py ${SDK_OUTPUT}/${SDKPATH}
 }
+do_populate_sdk_ext[file-checksums] += "${COREBASE}/meta/files/ext-sdk-prepare.py:True"
 
 # Since bitbake won't run as root it doesn't make sense to try and install
 # the extensible sdk as root.
@@ -358,4 +359,8 @@ do_populate_sdk_ext[rdepends] += "${@' '.join([x + ':do_build' for x in d.getVar
 # Make sure codes change in copy_buildsystem can result in rebuilt
 do_populate_sdk_ext[vardeps] += "copy_buildsystem"
 
+do_populate_sdk_ext[file-checksums] += "${COREBASE}/meta/files/toolchain-shar-relocate.sh:True \
+                                        ${COREBASE}/meta/files/toolchain-shar-extract.sh:True \
+                                        ${COREBASE}/scripts/gen-lockedsig-cache:True"
+
 addtask populate_sdk_ext after do_sdk_depends
Signature · 1 line
2.5.0
#8 lore raw

PATCH 8/14 toolchain-shar-extract.sh: improve behaviour when xz is not installed

In reply to Paul Eggleton

Currently if xz isn't installed when installing the SDK you get an error from tar and then the SDK installation continues, cascading into a load of other failures since files are missing. Add a check to see if xz can be run and error out if it can't, and also exit if tar fails.

---
 meta/files/toolchain-shar-extract.sh | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
meta/files/toolchain-shar-extract.sh +6 -1
index b1ef4b7..d844771 100644
@@ -29,6 +29,11 @@ if [ "$INST_ARCH" != "$SDK_ARCH" ]; then
 	fi
 fi
 
+if ! xz -V > /dev/null 2>&1; then
+	echo "Error: xz is required for installation of this SDK, please install it first"
+	exit 1
+fi
+
 DEFAULT_INSTALL_DIR="@SDKPATH@"
 SUDO_EXEC=""
 target_sdk_dir=""
@@ -168,7 +173,7 @@ fi
 payload_offset=$(($(grep -na -m1 "^MARKER:$" $0|cut -d':' -f1) + 1))
 
 printf "Extracting SDK..."
-tail -n +$payload_offset $0| $SUDO_EXEC tar xJ -C $target_sdk_dir --checkpoint=.2500
+tail -n +$payload_offset $0| $SUDO_EXEC tar xJ -C $target_sdk_dir --checkpoint=.2500 || exit 1
 echo "done"
 
 printf "Setting it up..."
Signature · 1 line
2.5.0
#9 lore raw

PATCH 9/14 gen-lockedsig-cache: copy correct native sstate into ext SDK

In reply to Paul Eggleton

When constructing the sstate-cache directory for the extensible SDK, we were copying in any matching native sstate packages, and as the signature doesn't actually change when the distro changes (since NATIVELSBSTRING is just a path separator for the artifacts and is not part of the signature) we ended up copying duplicated packages when the distro changed e.g. upon host distro upgrade. Only search in the NATIVELSBSTRING-named subdirectory for native packages and the issue goes away.

Fixes [YOCTO #8885].

---
 meta/lib/oe/copy_buildsystem.py | 5 +++--
 scripts/gen-lockedsig-cache     | 6 +++---
 2 files changed, 6 insertions(+), 5 deletions(-)
meta/lib/oe/copy_buildsystem.py +3 -2
index 6475510..fb51b51 100644
@@ -149,8 +149,9 @@ def merge_lockedsigs(copy_tasks, lockedsigs_main, lockedsigs_extra, merged_outpu
 def create_locked_sstate_cache(lockedsigs, input_sstate_cache, output_sstate_cache, d, fixedlsbstring=""):
     bb.note('Generating sstate-cache...')
 
-    bb.process.run("gen-lockedsig-cache %s %s %s" % (lockedsigs, input_sstate_cache, output_sstate_cache))
+    nativelsbstring = d.getVar('NATIVELSBSTRING', True)
+    bb.process.run("gen-lockedsig-cache %s %s %s %s" % (lockedsigs, input_sstate_cache, output_sstate_cache, nativelsbstring))
     if fixedlsbstring:
-        nativedir = output_sstate_cache + '/' + d.getVar('NATIVELSBSTRING', True)
+        nativedir = output_sstate_cache + '/' + nativelsbstring
         if os.path.isdir(nativedir):
             os.rename(nativedir, output_sstate_cache + '/' + fixedlsbstring)
scripts/gen-lockedsig-cache +3 -3
index 9c16506..a4e9ded 100755
@@ -13,9 +13,9 @@ def mkdir(d):
         if e.errno != errno.EEXIST:
             raise e
 
-if len(sys.argv) < 3:
+if len(sys.argv) < 5:
     print("Incorrect number of arguments specified")
-    print("syntax: gen-lockedsig-cache <locked-sigs.inc> <input-cachedir> <output-cachedir>")
+    print("syntax: gen-lockedsig-cache <locked-sigs.inc> <input-cachedir> <output-cachedir> <nativelsbstring>")
     sys.exit(1)
 
 print('Reading %s' % sys.argv[1])
@@ -30,7 +30,7 @@ files = set()
 for s in sigs:
     p = sys.argv[2] + "/" + s[:2] + "/*" + s + "*"
     files |= set(glob.glob(p))
-    p = sys.argv[2] + "/*/" + s[:2] + "/*" + s + "*"
+    p = sys.argv[2] + "/%s/" % sys.argv[4] + s[:2] + "/*" + s + "*"
     files |= set(glob.glob(p))
 
 print('Processing files')
Signature · 1 line
2.5.0
#10 lore raw

PATCH 10/14 recipetool: create: strip quotes from values extracted from CMakeLists.txt

In reply to Paul Eggleton

Quoting is optional in CMakeLists.txt and is occasionally used, so strip out quotes if they are present.

---
 scripts/lib/recipetool/create_buildsys.py | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)
scripts/lib/recipetool/create_buildsys.py +8 -5
index 6afb5de..ba393a8 100644
@@ -142,6 +142,9 @@ class CmakeRecipeHandler(RecipeHandler):
         subdir_re = re.compile('add_subdirectory\s*\(\s*([^)\s]*)\s*([^)\s]*)\s*\)', re.IGNORECASE)
         dep_re = re.compile('([^ ><=]+)( *[<>=]+ *[^ ><=]+)?')
 
+        def interpret_value(value):
+            return value.strip('"')
+
         def parse_cmake_file(fn, paths=None):
             searchpaths = (paths or []) + [os.path.dirname(fn)]
             logger.debug('Parsing file %s' % fn)
@@ -166,13 +169,13 @@ class CmakeRecipeHandler(RecipeHandler):
                         continue
                     res = proj_re.match(line)
                     if res:
-                        extravalues['PN'] = res.group(1).split()[0]
+                        extravalues['PN'] = interpret_value(res.group(1).split()[0])
                         continue
                     res = pkgcm_re.match(line)
                     if res:
                         res = dep_re.findall(res.group(2))
                         if res:
-                            pcdeps.extend([x[0] for x in res])
+                            pcdeps.extend([interpret_value(x[0]) for x in res])
                         inherits.append('pkgconfig')
                         continue
                     res = pkgsm_re.match(line)
@@ -180,7 +183,7 @@ class CmakeRecipeHandler(RecipeHandler):
                         res = dep_re.findall(res.group(2))
                         if res:
                             # Note: appending a tuple here!
-                            item = tuple((x[0] for x in res))
+                            item = tuple((interpret_value(x[0]) for x in res))
                             if len(item) == 1:
                                 item = item[0]
                             pcdeps.append(item)
@@ -189,7 +192,7 @@ class CmakeRecipeHandler(RecipeHandler):
                     res = findpackage_re.match(line)
                     if res:
                         origpkg = res.group(1)
-                        pkg = origpkg.lower()
+                        pkg = interpret_value(origpkg.lower())
                         if pkg == 'gettext':
                             inherits.append('gettext')
                         elif pkg == 'perl':
@@ -209,7 +212,7 @@ class CmakeRecipeHandler(RecipeHandler):
                         continue
                     res = checklib_re.match(line)
                     if res:
-                        lib = res.group(1)
+                        lib = interpret_value(res.group(1))
                         if not lib.startswith('$'):
                             libdeps.append(lib)
                     if line.lower().startswith('useswig'):
Signature · 1 line
2.5.0
#11 lore raw

PATCH 11/14 devtool / recipetool: support specifying a subdirectory within the fetched source

In reply to Paul Eggleton

Sometimes you don't want to build an entire project, just a subdirectory of it; add a --src-subdir option to make that easier. (We still look for a single subdirectory in what gets unpacked, e.g. what you might find within a tarball, so whatever you specify with this option is added onto the end of that.)

---
 scripts/lib/devtool/standard.py  |  6 ++++++
 scripts/lib/recipetool/create.py | 11 +++++++++--
 2 files changed, 15 insertions(+), 2 deletions(-)
scripts/lib/devtool/standard.py +6 -0
index 5390f51..f19de27 100644
@@ -141,6 +141,8 @@ def add(args, config, basepath, workspace):
         extracmdopts += ' -b'
     if args.also_native:
         extracmdopts += ' --also-native'
+    if args.src_subdir:
+        extracmdopts += ' --src-subdir "%s"' % args.src_subdir
 
     tempdir = tempfile.mkdtemp(prefix='devtool')
     try:
@@ -208,6 +210,9 @@ def add(args, config, basepath, workspace):
     if not rd:
         return 1
 
+    if args.src_subdir:
+        srctree = os.path.join(srctree, args.src_subdir)
+
     bb.utils.mkdirhier(os.path.dirname(appendfile))
     with open(appendfile, 'w') as f:
         f.write('inherit externalsrc\n')
@@ -1308,6 +1313,7 @@ def register_commands(subparsers, context):
     parser_add.add_argument('--no-git', '-g', help='If fetching source, do not set up source tree as a git repository', action="store_true")
     parser_add.add_argument('--binary', '-b', help='Treat the source tree as something that should be installed verbatim (no compilation, same directory structure). Useful with binary packages e.g. RPMs.', action='store_true')
     parser_add.add_argument('--also-native', help='Also add native variant (i.e. support building recipe for the build host as well as the target machine)', action='store_true')
+    parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
     parser_add.set_defaults(func=add)
 
     parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
scripts/lib/recipetool/create.py +9 -2
index 43861ee..9c3a63d 100644
@@ -324,6 +324,12 @@ def create_recipe(args):
         srcuri = ''
         srctree = args.source
 
+    if args.src_subdir:
+        srcsubdir = os.path.join(srcsubdir, args.src_subdir)
+        srctree_use = os.path.join(srctree, args.src_subdir)
+    else:
+        srctree_use = srctree
+
     if args.outfile and os.path.isdir(args.outfile):
         outfile = None
         outdir = args.outfile
@@ -343,7 +349,7 @@ def create_recipe(args):
     lines_before.append('# (Feel free to remove these comments when editing.)')
     lines_before.append('#')
 
-    licvalues = guess_license(srctree)
+    licvalues = guess_license(srctree_use)
     lic_files_chksum = []
     if licvalues:
         licenses = []
@@ -472,7 +478,7 @@ def create_recipe(args):
 
     extravalues = {}
     for handler in handlers:
-        handler.process(srctree, classes, lines_before, lines_after, handled, extravalues)
+        handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
 
     if not realpv:
         realpv = extravalues.get('PV', None)
@@ -759,5 +765,6 @@ def register_commands(subparsers):
     parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
     parser_create.add_argument('-b', '--binary', help='Treat the source tree as something that should be installed verbatim (no compilation, same directory structure)', action='store_true')
     parser_create.add_argument('--also-native', help='Also add native variant (i.e. support building recipe for the build host as well as the target machine)', action='store_true')
+    parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
     parser_create.set_defaults(func=create_recipe)
 
Signature · 1 line
2.5.0
#12 lore raw

PATCH 12/14 recipetool: create: extract SRC_URI from local git repositories

In reply to Paul Eggleton

If you specify a local directory which happens to be a git repository with an origin remote (and it is in fact remote), we can use that for SRC_URI rather than leaving it blank in the recipe.

---
 scripts/lib/recipetool/create.py | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)
scripts/lib/recipetool/create.py +23 -7
index 9c3a63d..5caf374 100644
@@ -260,6 +260,14 @@ def supports_srcrev(uri):
             return True
     return False
 
+def reformat_git_uri(uri):
+    '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
+    res = re.match('(https?)://([^;]+\.git)(;.*)?$', uri)
+    if res:
+        # Need to switch the URI around so that the git fetcher is used
+        return 'git://%s;protocol=%s%s' % (res.group(2), res.group(1), res.group(3) or '')
+    return uri
+
 def create_recipe(args):
     import bb.process
     import tempfile
@@ -275,16 +283,11 @@ def create_recipe(args):
     srcrev = '${AUTOREV}'
     if '://' in args.source:
         # Fetch a URL
-        fetchuri = urlparse.urldefrag(args.source)[0]
+        fetchuri = reformat_git_uri(urlparse.urldefrag(args.source)[0])
         if args.binary:
             # Assume the archive contains the directory structure verbatim
             # so we need to extract to a subdirectory
             fetchuri += ';subdir=%s' % os.path.splitext(os.path.basename(urlparse.urlsplit(fetchuri).path))[0]
-        git_re = re.compile('(https?)://([^;]+\.git)(;.*)?$')
-        res = git_re.match(fetchuri)
-        if res:
-            # Need to switch the URI around so that the git fetcher is used
-            fetchuri = 'git://%s;protocol=%s%s' % (res.group(2), res.group(1), res.group(3) or '')
         srcuri = fetchuri
         rev_re = re.compile(';rev=([^;]+)')
         res = rev_re.search(srcuri)
@@ -321,8 +324,21 @@ def create_recipe(args):
         if not os.path.isdir(args.source):
             logger.error('Invalid source directory %s' % args.source)
             sys.exit(1)
-        srcuri = ''
         srctree = args.source
+        srcuri = ''
+        if os.path.exists(os.path.join(srctree, '.git')):
+            # Try to get upstream repo location from origin remote
+            try:
+                stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
+            except bb.process.ExecutionError as e:
+                stdout = None
+            if stdout:
+                for line in stdout.splitlines():
+                    splitline = line.split()
+                    if len(splitline) > 1:
+                        if splitline[0] == 'origin' and '://' in splitline[1]:
+                            srcuri = reformat_git_uri(splitline[1])
+                            break
 
     if args.src_subdir:
         srcsubdir = os.path.join(srcsubdir, args.src_subdir)
Signature · 1 line
2.5.0
#13 lore raw

PATCH 13/14 recipetool: create: fix extraction of name from URLs ending in /

In reply to Paul Eggleton

If the URL ends in a / then we want to strip that off the path we split out of the URL before calling os.path.basename() on it.

---
 scripts/lib/recipetool/create.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
scripts/lib/recipetool/create.py +1 -1
index 5caf374..f0bb58e 100644
@@ -432,7 +432,7 @@ def create_recipe(args):
     if srcuri and not realpv or not pn:
         parseres = urlparse.urlparse(srcuri)
         if parseres.path:
-            srcfile = os.path.basename(parseres.path)
+            srcfile = os.path.basename(parseres.path.rstrip('/'))
             name_pn, name_pv = determine_from_filename(srcfile)
             logger.debug('Determined from filename: name = "%s", version = "%s"' % (name_pn, name_pv))
             if name_pn and not pn:
Signature · 1 line
2.5.0
#14 lore raw

PATCH 14/14 recipetool: create: better fix for fetch error handling

In reply to Paul Eggleton

I was a little bit hasty in OE-Core revision c2cc5abe34169eae92067d97ce1e747e7c1413f5 - it turns out BitBake's fetcher code is not consistent in whether it logs something useful or not; when fetching from an http URL it does but with a git repository it doesn't. In advance of any major reworking of fetch error handling in BitBake, let's just print the text of the exception and then we know we have shown something to the user.

Additionally, we were only catching FetchException here but there are several other classes of exception that the fetcher can raise (e.g. MalformedUrl); catch the parent BBFetchException class instead so we avoid tracebacks for those other classes as well.

---
 scripts/lib/recipetool/create.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
scripts/lib/recipetool/create.py +2 -2
index f0bb58e..5f90b10 100644
@@ -299,8 +299,8 @@ def create_recipe(args):
         logger.info('Fetching %s...' % srcuri)
         try:
             checksums = scriptutils.fetch_uri(tinfoil.config_data, fetchuri, srctree, srcrev)
-        except bb.fetch2.FetchError:
-            # Error already printed
+        except bb.fetch2.BBFetchException as e:
+            logger.error(str(e).rstrip())
             sys.exit(1)
         dirlist = os.listdir(srctree)
         if 'git.indirectionsymlink' in dirlist:
Signature · 1 line
2.5.0