diff options
-rw-r--r-- | meta/classes/devtool-source.bbclass | 56 | ||||
-rw-r--r-- | meta/lib/oe/recipeutils.py | 4 | ||||
-rw-r--r-- | meta/lib/oe/utils.py | 24 | ||||
-rw-r--r-- | meta/lib/oeqa/selftest/cases/devtool.py | 2 | ||||
-rwxr-xr-x | scripts/devtool | 63 | ||||
-rw-r--r-- | scripts/lib/devtool/__init__.py | 14 | ||||
-rw-r--r-- | scripts/lib/devtool/standard.py | 383 | ||||
-rw-r--r-- | scripts/lib/devtool/upgrade.py | 1 |
8 files changed, 406 insertions, 141 deletions
diff --git a/meta/classes/devtool-source.bbclass b/meta/classes/devtool-source.bbclass index 41900e651f0..90b50fb4d5d 100644 --- a/meta/classes/devtool-source.bbclass +++ b/meta/classes/devtool-source.bbclass @@ -69,7 +69,7 @@ python devtool_post_unpack() { import shutil sys.path.insert(0, os.path.join(d.getVar('COREBASE'), 'scripts', 'lib')) import scriptutils - from devtool import setup_git_repo + from devtool import setup_git_repo, find_git_repos tempdir = d.getVar('DEVTOOL_TEMPDIR') workdir = d.getVar('WORKDIR') @@ -108,7 +108,26 @@ python devtool_post_unpack() { shutil.copy2(cfg, workdir) # Ignore local files with subdir={BP} + excludeitems = recipe_patches + list(local_files.keys()) + pthvars = ['RECIPE_SYSROOT', 'RECIPE_SYSROOT_NATIVE', 'S'] + for pthvar in pthvars: + relp = os.path.relpath(d.getVar(pthvar), d.getVar('WORKDIR')) + if not relp.startswith('..'): + excludeitems.append(relp.split(os.sep)[0]) + extradirs = [] srcabspath = os.path.abspath(srcsubdir) + if srcabspath != os.path.abspath(workdir): + for pth in os.listdir(workdir): + if pth in excludeitems: + continue + wpth = os.path.join(workdir, pth) + if os.path.isdir(wpth) and os.listdir(wpth): + extradirs.append(wpth) + + repos = find_git_repos(srcabspath) + extradirs.extend(repos) + + # Ignore local files with subdir={BP} local_files = [fname for fname in local_files if os.path.exists(os.path.join(workdir, fname)) and (srcabspath == workdir or not @@ -151,11 +170,20 @@ python devtool_post_unpack() { (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srcsubdir) initial_rev = stdout.rstrip() + + initial_revs = {} + for extradir in extradirs: + setup_git_repo(extradir, d.getVar('PV'), devbranch, d=d) + (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=extradir) + initial_revs[extradir] = stdout.rstrip() + with open(os.path.join(tempdir, 'initial_rev'), 'w') as f: f.write(initial_rev) with open(os.path.join(tempdir, 'srcsubdir'), 'w') as f: - f.write(srcsubdir) + f.write('%s\n' % srcsubdir) + for extradir in extradirs: + f.write('%s=%s\n' % (extradir, initial_revs[extradir])) } python devtool_pre_patch() { @@ -166,18 +194,25 @@ python devtool_pre_patch() { python devtool_post_patch() { import shutil tempdir = d.getVar('DEVTOOL_TEMPDIR') + + srcdirs = [] with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f: - srcsubdir = f.read() + for line in f: + line = line.rstrip() + if line: + srcdirs.append(line.split('=')[0]) + srcsubdir = srcdirs[0] + with open(os.path.join(tempdir, 'initial_rev'), 'r') as f: initial_rev = f.read() - def rm_patches(): - patches_dir = os.path.join(srcsubdir, 'patches') + def rm_patches(pth): + patches_dir = os.path.join(pth, 'patches') if os.path.exists(patches_dir): shutil.rmtree(patches_dir) # Restore any "patches" directory that was actually part of the source tree try: - bb.process.run('git checkout -- patches', cwd=srcsubdir) + bb.process.run('git checkout -- patches', cwd=pth) except bb.process.ExecutionError: pass @@ -201,7 +236,7 @@ python devtool_post_patch() { localdata.setVar('OVERRIDES', ':'.join(no_overrides)) localdata.setVar('FILESOVERRIDES', ':'.join(no_overrides)) bb.build.exec_func('do_patch', localdata) - rm_patches() + rm_patches(srcsubdir) # Now we need to reconcile the dev branch with the no-overrides one # (otherwise we'd likely be left with identical commits that have different hashes) bb.process.run('git checkout %s' % devbranch, cwd=srcsubdir) @@ -220,12 +255,15 @@ python devtool_post_patch() { localdata.setVar('OVERRIDES', ':'.join(no_overrides + [override])) localdata.setVar('FILESOVERRIDES', ':'.join(no_overrides + [override])) bb.build.exec_func('do_patch', localdata) - rm_patches() + rm_patches(srcsubdir) # Now we need to reconcile the new branch with the no-overrides one # (otherwise we'd likely be left with identical commits that have different hashes) bb.process.run('git rebase devtool-no-overrides', cwd=srcsubdir) bb.process.run('git checkout %s' % devbranch, cwd=srcsubdir) - bb.process.run('git tag -f devtool-patched', cwd=srcsubdir) + for srcdir in srcdirs: + bb.process.run('git tag -f devtool-patched', cwd=srcdir) + if srcdir != srcsubdir: + rm_patches(srcdir) } python devtool_post_configure() { diff --git a/meta/lib/oe/recipeutils.py b/meta/lib/oe/recipeutils.py index 3594db31abf..de71d8d7bfc 100644 --- a/meta/lib/oe/recipeutils.py +++ b/meta/lib/oe/recipeutils.py @@ -506,10 +506,10 @@ def get_recipe_patches(d): """Get a list of the patches included in SRC_URI within a recipe.""" import oe.patch patches = oe.patch.src_patches(d, expand=False) - patchfiles = [] + patchfiles = OrderedDict() for patch in patches: _, _, local, _, _, parm = bb.fetch.decodeurl(patch) - patchfiles.append(local) + patchfiles[local] = parm return patchfiles diff --git a/meta/lib/oe/utils.py b/meta/lib/oe/utils.py index 9a2187e36f8..6bc4bd5eecd 100644 --- a/meta/lib/oe/utils.py +++ b/meta/lib/oe/utils.py @@ -536,3 +536,27 @@ class ImageQAFailed(Exception): def sh_quote(string): import shlex return shlex.quote(string) + +def is_path_under(path1, possible_parent): + """ + Return True if a path is underneath another, False otherwise. + Multiple paths to test can be specified (as a list or tuple) in + which case all specified test paths must be under the parent to + return True. + """ + def abs_path_trailing(pth): + pth_abs = os.path.abspath(pth) + if not pth_abs.endswith(os.sep): + pth_abs += os.sep + return pth_abs + + possible_parent_abs = abs_path_trailing(possible_parent) + if isinstance(path1, str): + testpaths = [path1] + else: + testpaths = path1 + for path in testpaths: + path_abs = abs_path_trailing(path) + if not path_abs.startswith(possible_parent_abs): + return False + return True diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index e9b466f4eb8..21eb0029aad 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -11,7 +11,7 @@ import fnmatch import oeqa.utils.ftools as ftools from oeqa.selftest.case import OESelftestTestCase -from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer +from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer, CommandError from oeqa.utils.commands import get_bb_vars, runqemu, get_test_layer oldmetapath = None diff --git a/scripts/devtool b/scripts/devtool index 8a4f41bc372..cfc26cfb855 100755 --- a/scripts/devtool +++ b/scripts/devtool @@ -15,6 +15,8 @@ import re import configparser import subprocess import logging +import json +from collections import OrderedDict basepath = '' workspace = {} @@ -99,32 +101,59 @@ def read_workspace(): if not context.fixed_setup: _enable_workspace_layer(config.workspace_path, config, basepath) + def process_inline_json(strvalue, in_value): + if strvalue.count('{') == strvalue.count('}') and strvalue.count('[') == strvalue.count(']'): + pnvalues[in_value] = json.loads(strvalue, object_pairs_hook=OrderedDict) + return True + return False + logger.debug('Reading workspace in %s' % config.workspace_path) externalsrc_re = re.compile(r'^EXTERNALSRC(_pn-([^ =]+))? *= *"([^"]*)"$') for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')): with open(fn, 'r') as f: pnvalues = {} + in_value = None + strvalue = '' for line in f: - res = externalsrc_re.match(line.rstrip()) - if res: - recipepn = os.path.splitext(os.path.basename(fn))[0].split('_')[0] - pn = res.group(2) or recipepn - # Find the recipe file within the workspace, if any - bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*') - recipefile = glob.glob(os.path.join(config.workspace_path, - 'recipes', - recipepn, - bbfile)) - if recipefile: - recipefile = recipefile[0] - pnvalues['srctree'] = res.group(3) - pnvalues['bbappend'] = fn - pnvalues['recipefile'] = recipefile - elif line.startswith('# srctreebase: '): - pnvalues['srctreebase'] = line.split(':', 1)[1].strip() + if in_value: + if not line.startswith('#'): + logger.error('Syntax error in %s - invalid in-line json' % fn) + sys.exit(1) + strvalue += line[1:] + if process_inline_json(strvalue, in_value): + in_value = None + else: + res = externalsrc_re.match(line.rstrip()) + if res: + recipepn = os.path.splitext(os.path.basename(fn))[0].split('_')[0] + pn = res.group(2) or recipepn + # Find the recipe file within the workspace, if any + bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*') + recipefile = glob.glob(os.path.join(config.workspace_path, + 'recipes', + recipepn, + bbfile)) + if recipefile: + recipefile = recipefile[0] + pnvalues['srctree'] = res.group(3) + pnvalues['bbappend'] = fn + pnvalues['recipefile'] = recipefile + elif line.startswith('# srctreebase: '): + pnvalues['srctreebase'] = line.split(':', 1)[1].strip() + elif line.startswith('# srctreetop: '): + pnvalues['srctreetop'] = line.split(':', 1)[1].strip() + elif line.startswith('# extradirs: '): + strvalue = line.split(':', 1)[1].strip() + in_value = 'extradirs' + if process_inline_json(strvalue, in_value): + in_value = None if pnvalues: if not pnvalues.get('srctreebase', None): pnvalues['srctreebase'] = pnvalues['srctree'] + if not 'srctreetop' in pnvalues: + pnvalues['srctreetop'] = pnvalues['srctreebase'] + if not 'extradirs' in pnvalues: + pnvalues['extradirs'] = [] logger.debug('Found recipe %s' % pnvalues) workspace[pn] = pnvalues diff --git a/scripts/lib/devtool/__init__.py b/scripts/lib/devtool/__init__.py index 702db669de3..e5fa34a23ea 100644 --- a/scripts/lib/devtool/__init__.py +++ b/scripts/lib/devtool/__init__.py @@ -233,6 +233,20 @@ def setup_git_repo(repodir, version, devbranch, basetag='devtool-base', d=None): bb.process.run('git checkout -b %s' % devbranch, cwd=repodir) bb.process.run('git tag -f %s' % basetag, cwd=repodir) +def find_git_repos(pth, toplevel=False): + """ + Find git repositories under a path + """ + repos = [] + if toplevel and os.path.isdir(os.path.join(pth, '.git')): + repos.append(pth) + for root, dirs, _ in os.walk(pth): + for dfn in dirs: + dfp = os.path.join(root, dfn) + if os.path.isdir(os.path.join(dfp, '.git')) and dfp not in repos: + repos.append(dfp) + return repos + def recipe_to_append(recipefile, config, wildcard=False): """ Convert a recipe file to a bbappend file path within the workspace. diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py index 554542ed4cf..f8a138e655f 100644 --- a/scripts/lib/devtool/standard.py +++ b/scripts/lib/devtool/standard.py @@ -19,8 +19,9 @@ import scriptutils import errno import glob import filecmp +import json from collections import OrderedDict, namedtuple -from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, update_unlockedsigs, check_prerelease_version, check_git_repo_dirty, check_git_repo_op, DevtoolError +from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, update_unlockedsigs, check_prerelease_version, check_git_repo_dirty, check_git_repo_op, find_git_repos, DevtoolError from devtool import parse_recipe logger = logging.getLogger('devtool') @@ -479,7 +480,7 @@ def symlink_oelocal_files_srctree(rd,srctree): bb.process.run('git %s commit -m "Committing local file symlinks\n\n%s"' % (' '.join(useroptions), oe.patch.GitApplyTree.ignore_commit_prefix), cwd=srctree) -ExtractSourceResult = namedtuple('ExtractSourceResult', ['initial_rev', 'srcsubdir']) +ExtractSourceResult = namedtuple('ExtractSourceResult', ['initial_rev', 'srcsubdir', 'extradirs']) def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False): """Extract sources of a recipe""" @@ -598,15 +599,27 @@ def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, works if not res: raise DevtoolError('Extracting source for %s failed' % pn) + tempworkdir = os.path.join(tempdir, 'workdir') + extra_revs = OrderedDict() + extradirs = [] try: with open(os.path.join(tempdir, 'initial_rev'), 'r') as f: initial_rev = f.read() with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f: - srcsubdir = f.read() + srcsubdir = f.readline().rstrip() + for line in f: + line = line.rstrip() + if '=' in line: + linesplit = line.rstrip().split('=', 1) + extradir = linesplit[0] + extradirs.append(extradir) + extra_revs[os.path.relpath(extradir, tempworkdir)] = linesplit[1] + except FileNotFoundError as e: raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e)) - srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir')) + + srcsubdir_rel = os.path.relpath(srcsubdir, tempworkdir) # Check if work-shared is empty, if yes # find source and copy to work-shared @@ -652,12 +665,25 @@ def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, works logger.info('Adding local source files to srctree...') shutil.move(tempdir_localdir, srcsubdir) + if extra_revs: + logger.warn('This recipe fetches more than one source tree. Each source tree will be tracked in a separate git repository.') + if not oe.utils.is_path_under(extradirs, srcsubdir): + # There's more than one source directory at the top level, we're going to need to create the parent + os.mkdir(srctree) + for extradir in extradirs: + shutil.move(extradir, srctree) + # Write a file to record which one is the main src in case we need to re-modify the same tree later + with open(os.path.join(srctree, '.devtool'), 'w') as f: + data = OrderedDict() + data['srcsubdir'] = os.path.basename(srcsubdir) + data['extradirs'] = extra_revs + json.dump(data, f, indent=' ') shutil.move(srcsubdir, srctree) symlink_oelocal_files_srctree(d,srctree) if is_kernel_yocto: logger.info('Copying kernel config to srctree') - shutil.copy2(os.path.join(tempdir, '.config'), srctree) + shutil.copy2(os.path.join(tempdir, '.config'), os.path.join(srctree, srcsubdir_rel)) finally: if appendbackup: @@ -668,7 +694,7 @@ def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, works logger.info('Preserving temporary directory %s' % tempdir) else: shutil.rmtree(tempdir) - return ExtractSourceResult(initial_rev, srcsubdir_rel) + return ExtractSourceResult(initial_rev, srcsubdir_rel, extra_revs) def _add_md5(config, recipename, filename): """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace""" @@ -740,6 +766,32 @@ def get_staging_kbranch(srcdir): staging_kbranch = "".join(branch.split('\n')[0]) return staging_kbranch +def _get_initial_rev(srctree): + """ + Given a source tree, get the initial revision and determine if it was prepared by devtool + """ + initial_rev = None + # Check if it's a tree previously extracted by us. This is done + # by ensuring that devtool-base and args.branch (devtool) exist. + # The check_commits logic will cause an exception if either one + # of these doesn't exist + try: + (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree) + bb.process.run('git rev-parse %s' % args.branch, cwd=srctree) + except bb.process.ExecutionError: + stdout = '' + if stdout: + was_devtool = True + for line in stdout.splitlines(): + if line.startswith('*'): + (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree) + initial_rev = stdout.rstrip() + if not initial_rev: + # Otherwise, just grab the head revision + (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree) + initial_rev = stdout.rstrip() + return initial_rev, was_devtool + def modify(args, config, basepath, workspace): """Entry point for the devtool 'modify' subcommand""" import bb @@ -841,33 +893,40 @@ def modify(args, config, basepath, workspace): args.no_extract = True torev = 'HEAD' + extradirs = OrderedDict() + srctreetop = None + extra_revs = {} if not args.no_extract: ret = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides) initial_rev = ret.initial_rev + extradirs = ret.extradirs + if extradirs and not oe.utils.is_path_under(extradirs.keys(), ret.srcsubdir): + # Multiple source trees, we need to be inside the main one + srctreetop = srctree + srctree = os.path.join(srctree, ret.srcsubdir) logger.info('Source tree extracted to %s' % srctree) check_commits = True else: + devtoolfile = os.path.join(srctree, '.devtool') + if os.path.exists(devtoolfile): + with open(devtoolfile, 'r') as f: + cfg = json.load(f, object_pairs_hook=OrderedDict) + srcsubdir = cfg.get('srcsubdir', '') + if srcsubdir: + srctreetop = srctree + srctree = os.path.abspath(os.path.join(srctree, srcsubdir)) + extradirs.update(cfg.get('extradirs', {})) + else: + # Find any extra source trees + extradirpaths = find_git_repos(srctree) + for extradirpath in extradirpaths: + extradirs[os.path.relpath(extradirpath, srctree)], _ = _get_initial_rev(extradirpath) + if os.path.exists(os.path.join(srctree, '.git')): - # Check if it's a tree previously extracted by us. This is done - # by ensuring that devtool-base and args.branch (devtool) exist. - # The check_commits logic will cause an exception if either one - # of these doesn't exist - try: - (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree) - bb.process.run('git rev-parse %s' % args.branch, cwd=srctree) - except bb.process.ExecutionError: - stdout = '' - if stdout: - check_commits = True + # Check if it's a tree previously extracted by us + initial_rev, check_commits = _get_initial_rev(srctree) + if check_commits: torev = 'devtool-patched' - for line in stdout.splitlines(): - if line.startswith('*'): - (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree) - initial_rev = stdout.rstrip() - if not initial_rev: - # Otherwise, just grab the head revision - (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree) - initial_rev = stdout.rstrip() if initial_rev: # Get list of commits since this revision @@ -901,6 +960,23 @@ def modify(args, config, basepath, workspace): # Need to grab this here in case the source is within a subdirectory srctreebase = srctree + if extradirs: + extradirentries = [] + for extradir, extrarev in extradirs.items(): + extradirentry = OrderedDict() + if srctreetop: + # Several source trees at the top level + extradirentry['path'] = os.path.join(srctreetop, extradir) + else: + # All of the extra source trees are under the main one, so chop off + # the top level subdirectory + extradirentry['path'] = os.path.join(srctree, os.sep.join(extradir.split(os.sep)[1:])) + extradirentry['initial_rev'] = extrarev + # Get list of commits since this revision + (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % extrarev, cwd=extradirentry['path']) + extradirentry['commits'] = stdout.split() + extradirentries.append(extradirentry) + # Check that recipe isn't using a shared workdir s = os.path.abspath(rd.getVar('S')) workdir = os.path.abspath(rd.getVar('WORKDIR')) @@ -941,6 +1017,23 @@ def modify(args, config, basepath, workspace): ' ln -sfT ${B}/.config ${S}/.config.new\n' ' fi\n' '}\n') + + if extradirs: + f.write('\n# srctreetop: %s\n' % srctreetop) + extradir_json = json.dumps(extradirentries, indent=' ') + prefix = '# extradirs:' + for line in extradir_json.splitlines(): + f.write('%s %s\n' % (prefix, line)) + prefix = '#' + f.write('\ndo_unpack[postfuncs] += "devtool_symlink_srctrees"\n') + f.write('devtool_symlink_srctrees () {\n') + # We could have done a loop in the function itself, but given there's + # usually only going to be a small number, there's not much point + for extradiritem in extradirentries: + if not oe.utils.is_path_under(extradiritem['path'], srctree): + f.write(' ln -sf %s ${WORKDIR}/\n' % extradiritem['path']) + f.write('}\n') + if initial_rev: f.write('\n# initial_rev: %s\n' % initial_rev) for commit in commits: @@ -1155,7 +1248,7 @@ def rename(args, config, basepath, workspace): return 0 -def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False): +def _get_patchset_revs(recipename, workspace, srctree, initial_rev=None, force_patch_refresh=False): """Get initial and update rev of a recipe. These are the start point of the whole patchset and start point for the patches to be re-generated/updated. """ @@ -1166,10 +1259,15 @@ def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refre cwd=srctree) branchname = stdout.rstrip() + append = workspace[recipename]['bbappend'] + if not os.path.exists(append): + raise DevtoolError('unable to find workspace bbappend for recipe %s' % + recipename) + # Parse initial rev from recipe if not specified commits = [] patches = [] - with open(recipe_path, 'r') as f: + with open(append, 'r') as f: for line in f: if line.startswith('# initial_rev:'): if not initial_rev: @@ -1179,6 +1277,15 @@ def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refre elif line.startswith('# patches_%s:' % branchname): patches = line.split(':')[-1].strip().split(',') + if srctree != workspace[recipename]['srctree']: + # Extra dir + for extradirentry in workspace[recipename]['extradirs']: + if srctree == extradirentry['path']: + commits = extradirentry['commits'] + break + else: + raise Exception('Failed to find extradir entry for %s' % srctree) + update_rev = initial_rev changed_revs = None if initial_rev: @@ -1259,7 +1366,7 @@ def _remove_source_files(append, files, destpath, no_report_remove=False, dry_ru raise -def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None): +def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None, origsrcdir=None): """Export patches from srctree to given location. Returns three-tuple of dicts: 1. updated - patches that already exist in SRCURI @@ -1274,9 +1381,21 @@ def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None): added = OrderedDict() seqpatch_re = re.compile('^([0-9]{4}-)?(.+)') + patches = oe.recipeutils.get_recipe_patches(rd) + if origsrcdir: + if not origsrcdir.endswith(os.sep): + origsrcdir = origsrcdir + os.sep + s = rd.getVar('S') + for patch, parms in list(patches.items()): + patchdir = os.path.abspath(os.path.join(s, parms.get('patchdir', '.'))) + if not patchdir.endswith(os.sep): + patchdir = patchdir + os.sep + if not patchdir.startswith(origsrcdir): + del patches[patch] + existing_patches = dict((os.path.basename(path), path) for path in - oe.recipeutils.get_recipe_patches(rd)) - logger.debug('Existing patches: %s' % existing_patches) + patches) + logger.debug('Existing patches (for %s): %s' % (srctree, existing_patches)) # Generate patches from Git, exclude local files directory patch_pathspec = _git_exclude_path(srctree, 'oe-local-files') @@ -1483,6 +1602,11 @@ def _update_recipe_srcrev(recipename, workspace, srctree, rd, appendlayerdir, wi recipedir = os.path.basename(recipefile) logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix)) + # Work out what the extracted path would have been (so we can use it + # to filter patches when there are extra source directories) + s = rd.getVar('S') + origsrcdir = os.path.abspath(os.path.join(s, os.path.relpath(srctree, workspace[recipename]['srctree']))) + # Get HEAD revision try: stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree) @@ -1511,7 +1635,8 @@ def _update_recipe_srcrev(recipename, workspace, srctree, rd, appendlayerdir, wi patches_dir = tempfile.mkdtemp(dir=tempdir) old_srcrev = rd.getVar('SRCREV') or '' upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev, - patches_dir) + patches_dir, + origsrcdir=origsrcdir) logger.debug('Patches: update %s, new %s, delete %s' % (dict(upd_p), dict(new_p), dict(del_p))) # Remove deleted local files and "overlapping" patches @@ -1557,6 +1682,10 @@ def _update_recipe_srcrev(recipename, workspace, srctree, rd, appendlayerdir, wi if update_srcuri: patchfields['SRC_URI'] = ' '.join(srcuri) ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir) + # Set the values into the datastore for the benefit of the next + # call (if extradirs are set) + for var, value in patchfields.items(): + rd.setVar(var, value) finally: shutil.rmtree(tempdir) if not 'git://' in orig_src_uri: @@ -1574,12 +1703,8 @@ def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wil recipefile = rd.getVar('FILE') recipedir = os.path.dirname(recipefile) - append = workspace[recipename]['bbappend'] - if not os.path.exists(append): - raise DevtoolError('unable to find workspace bbappend for recipe %s' % - recipename) - initial_rev, update_rev, changed_revs, filter_patches = _get_patchset_revs(srctree, append, initial_rev, force_patch_refresh) + initial_rev, update_rev, changed_revs, filter_patches = _get_patchset_revs(recipename, workspace, srctree, initial_rev, force_patch_refresh) if not initial_rev: raise DevtoolError('Unable to find initial revision - please specify ' 'it with --initial-rev') @@ -1589,6 +1714,17 @@ def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wil if not dl_dir.endswith('/'): dl_dir += '/' + # Work out what the extracted path would have been (so we can use it + # to filter patches when there are extra source directories) + s = rd.getVar('S') + origsrcdir = os.path.abspath(os.path.join(s, os.path.relpath(srctree, workspace[recipename]['srctree']))) + # Determine patchdir for any new patches + patchdir = os.path.relpath(origsrcdir, s) + if patchdir != '.': + patchsuffix = ';patchdir=%s' % patchdir + else: + patchsuffix = '' + dry_run_suffix = ' (dry-run)' if dry_run_outdir else '' tempdir = tempfile.mkdtemp(prefix='devtool') @@ -1607,14 +1743,16 @@ def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wil # Get all patches from source tree and check if any should be removed all_patches_dir = tempfile.mkdtemp(dir=tempdir) _, _, del_p = _export_patches(srctree, rd, initial_rev, - all_patches_dir) + all_patches_dir, + origsrcdir=origsrcdir) # Remove deleted local files and patches remove_files = list(del_f.values()) + list(del_p.values()) # Get updated patches from source tree patches_dir = tempfile.mkdtemp(dir=tempdir) upd_p, new_p, _ = _export_patches(srctree, rd, update_rev, - patches_dir, changed_revs) + patches_dir, changed_revs, + origsrcdir=origsrcdir) logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p))) if filter_patches: new_p = OrderedDict() @@ -1644,7 +1782,7 @@ def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wil removevalues=removevalues, redirect_output=dry_run_outdir) else: - logger.info('No patches or local source files needed updating') + return False, None, [] else: # Update existing files files_dir = _determine_files_dir(rd) @@ -1666,7 +1804,7 @@ def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wil # replace the entry in SRC_URI with our local version logger.info('Replacing remote patch %s with updated local version' % basepath) path = os.path.join(files_dir, basepath) - _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath) + _replace_srcuri_entry(srcuri, basepath, 'file://%s%s' % (basepath, patchsuffix)) updaterecipe = True else: logger.info('Updating patch %s%s' % (basepath, dry_run_suffix)) @@ -1688,7 +1826,7 @@ def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wil os.path.join(files_dir, basepath), dry_run_outdir=dry_run_outdir, base_outdir=recipedir) - srcuri.append('file://%s' % basepath) + srcuri.append('file://%s%s' % (basepath, patchsuffix)) updaterecipe = True # Update recipe, if needed if _remove_file_entries(srcuri, remove_files)[0]: @@ -1699,9 +1837,11 @@ def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wil ret = oe.recipeutils.patch_recipe(rd, recipefile, {'SRC_URI': ' '.join(srcuri)}, redirect_output=dry_run_outdir) + # Set the value into the datastore for the benefit of the next + # call (if extradirs are set) + rd.setVar('SRC_URI', ' '.join(srcuri)) elif not updatefiles: # Neither patches nor recipe were updated - logger.info('No patches or files need updating') return False, None, [] finally: shutil.rmtree(tempdir) @@ -1732,70 +1872,84 @@ def _guess_recipe_update_mode(srctree, rdata): return 'patch' def _update_recipe(recipename, workspace, rd, mode, appendlayerdir, wildcard_version, no_remove, initial_rev, no_report_remove=False, dry_run_outdir=None, no_overrides=False, force_patch_refresh=False): - srctree = workspace[recipename]['srctree'] + main_srctree = workspace[recipename]['srctree'] if mode == 'auto': - mode = _guess_recipe_update_mode(srctree, rd) + mode = _guess_recipe_update_mode(main_srctree, rd) - override_branches = [] - mainbranch = None - startbranch = None - if not no_overrides: - stdout, _ = bb.process.run('git branch', cwd=srctree) - other_branches = [] - for line in stdout.splitlines(): - branchname = line[2:] - if line.startswith('* '): - startbranch = branchname - if branchname.startswith(override_branch_prefix): - override_branches.append(branchname) - else: - other_branches.append(branchname) + appendfile = workspace[recipename]['bbappend'] + srctrees = OrderedDict([(main_srctree, initial_rev)]) + for extradirentry in workspace[recipename]['extradirs']: + srctrees[extradirentry['path']] = extradirentry['initial_rev'] + + logger.debug('Considering source trees: %s' % srctrees) + if mode == 'auto': + mode = _guess_recipe_update_mode(main_srctree, rd) + + crd = bb.data.createCopy(rd) + for srctree, src_initial_rev in srctrees.items(): + override_branches = [] + mainbranch = None + startbranch = None + if not no_overrides: + stdout, _ = bb.process.run('git branch', cwd=srctree) + other_branches = [] + for line in stdout.splitlines(): + branchname = line[2:] + if line.startswith('* '): + startbranch = branchname + if branchname.startswith(override_branch_prefix): + override_branches.append(branchname) + else: + other_branches.append(branchname) + + if override_branches: + logger.debug('_update_recipe: override branches: %s' % override_branches) + logger.debug('_update_recipe: other branches: %s' % other_branches) + if startbranch.startswith(override_branch_prefix): + if len(other_branches) == 1: + mainbranch = other_branches[1] + else: + raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first') + else: + mainbranch = startbranch + + checkedout = None + anyupdated = False + appendfile = None + allremoved = [] if override_branches: - logger.debug('_update_recipe: override branches: %s' % override_branches) - logger.debug('_update_recipe: other branches: %s' % other_branches) - if startbranch.startswith(override_branch_prefix): - if len(other_branches) == 1: - mainbranch = other_branches[1] + logger.info('Handling main branch (%s)...' % mainbranch) + if startbranch != mainbranch: + bb.process.run('git checkout %s' % mainbranch, cwd=srctree) + checkedout = mainbranch + try: + branchlist = [mainbranch] + override_branches + for branch in branchlist: + if branch != mainbranch: + logger.info('Handling branch %s...' % branch) + override = branch[len(override_branch_prefix):] + crd.setVar('OVERRIDES', '%s:%s' % rd.getVar('OVERRIDES') + override) + bb.process.run('git checkout %s' % branch, cwd=srctree) + checkedout = branch + + if mode == 'srcrev': + updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir) + elif mode == 'patch': + updated, appendf, removed = _update_recipe_patch(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, src_initial_rev, dry_run_outdir, force_patch_refresh) else: - raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first') - else: - mainbranch = startbranch + raise DevtoolError('update_recipe: invalid mode %s' % mode) + if updated: + anyupdated = True + if appendf: + appendfile = appendf + allremoved.extend(removed) + finally: + if startbranch and checkedout != startbranch: + bb.process.run('git checkout %s' % startbranch, cwd=srctree) - checkedout = None - anyupdated = False - appendfile = None - allremoved = [] - if override_branches: - logger.info('Handling main branch (%s)...' % mainbranch) - if startbranch != mainbranch: - bb.process.run('git checkout %s' % mainbranch, cwd=srctree) - checkedout = mainbranch - try: - branchlist = [mainbranch] + override_branches - for branch in branchlist: - crd = bb.data.createCopy(rd) - if branch != mainbranch: - logger.info('Handling branch %s...' % branch) - override = branch[len(override_branch_prefix):] - crd.appendVar('OVERRIDES', ':%s' % override) - bb.process.run('git checkout %s' % branch, cwd=srctree) - checkedout = branch - - if mode == 'srcrev': - updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir) - elif mode == 'patch': - updated, appendf, removed = _update_recipe_patch(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, initial_rev, dry_run_outdir, force_patch_refresh) - else: - raise DevtoolError('update_recipe: invalid mode %s' % mode) - if updated: - anyupdated = True - if appendf: - appendfile = appendf - allremoved.extend(removed) - finally: - if startbranch and checkedout != startbranch: - bb.process.run('git checkout %s' % startbranch, cwd=srctree) + if not anyupdated: + logger.info('No patches or files need updating') return anyupdated, appendfile, allremoved @@ -1925,21 +2079,21 @@ def _reset(recipes, no_clean, remove_work, config, basepath, workspace): # We don't automatically create this dir next to appends, but the user can preservedir(os.path.join(config.workspace_path, 'appends', pn)) - srctreebase = workspace[pn]['srctreebase'] - if os.path.isdir(srctreebase): - if os.listdir(srctreebase): + srctreetop = workspace[pn]['srctreetop'] + if os.path.isdir(srctreetop): + if os.listdir(srctreetop): if remove_work: logger.info('-r argument used on %s, removing source tree.' ' You will lose any unsaved work' %pn) - shutil.rmtree(srctreebase) + shutil.rmtree(srctreetop) else: # We don't want to risk wiping out any work in progress logger.info('Leaving source tree %s as-is; if you no ' 'longer need it then please delete it manually' - % srctreebase) + % srctreetop) else: # This is unlikely, but if it's empty we can just remove it - os.rmdir(srctreebase) + os.rmdir(srctreetop) clean_preferred_provider(pn, config.workspace_path) @@ -2008,12 +2162,17 @@ def finish(args, config, basepath, workspace): srctree = workspace[args.recipename]['srctree'] check_git_repo_op(srctree, [corebasedir]) - dirty = check_git_repo_dirty(srctree) - if dirty: - if args.force: - logger.warning('Source tree is not clean, continuing as requested by -f/--force') - else: - raise DevtoolError('Source tree is not clean:\n\n%s\nEnsure you have committed your changes or use -f/--force if you are sure there\'s nothing that needs to be committed' % dirty) + srctrees = [srctree] + for extradirentry in workspace[args.recipename]['extradirs']: + srctrees.append(extradirentry['path']) + for pth in srctrees: + dirty = check_git_repo_dirty(pth) + if dirty: + if args.force: + logger.warning('Source tree %s is not clean, continuing as requested by -f/--force' % pth) + else: + raise DevtoolError('Source tree %s is not clean:\n\n%s\nEnsure you have committed your changes or use -f/--force if you are sure there\'s nothing that needs to be committed' % (pth, dirty)) + no_clean = args.no_clean remove_work=args.remove_work diff --git a/scripts/lib/devtool/upgrade.py b/scripts/lib/devtool/upgrade.py index 451fd19a4cf..94cba00a956 100644 --- a/scripts/lib/devtool/upgrade.py +++ b/scripts/lib/devtool/upgrade.py @@ -552,6 +552,7 @@ def upgrade(args, config, basepath, workspace): current_ret = standard._extract_source(srctree, False, 'devtool-orig', False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides) old_licenses = _extract_licenses(srctree, (rd.getVar('LIC_FILES_CHKSUM') or "")) logger.info('Extracting upgraded version source...') + srctree = os.path.join(srctree, current_ret.srcsubdir) rev2, md5, sha256, srcbranch, srcsubdir2 = _extract_new_source(args.version, srctree, args.no_patch, args.srcrev, args.srcbranch, args.branch, args.keep_temp, tinfoil, rd) |