1*4882a593Smuzhiyun# Development tool - standard commands plugin 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (C) 2014-2017 Intel Corporation 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun"""Devtool standard plugins""" 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunimport os 10*4882a593Smuzhiyunimport sys 11*4882a593Smuzhiyunimport re 12*4882a593Smuzhiyunimport shutil 13*4882a593Smuzhiyunimport subprocess 14*4882a593Smuzhiyunimport tempfile 15*4882a593Smuzhiyunimport logging 16*4882a593Smuzhiyunimport argparse 17*4882a593Smuzhiyunimport argparse_oe 18*4882a593Smuzhiyunimport scriptutils 19*4882a593Smuzhiyunimport errno 20*4882a593Smuzhiyunimport glob 21*4882a593Smuzhiyunimport filecmp 22*4882a593Smuzhiyunfrom collections import OrderedDict 23*4882a593Smuzhiyunfrom 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 24*4882a593Smuzhiyunfrom devtool import parse_recipe 25*4882a593Smuzhiyun 26*4882a593Smuzhiyunlogger = logging.getLogger('devtool') 27*4882a593Smuzhiyun 28*4882a593Smuzhiyunoverride_branch_prefix = 'devtool-override-' 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun 31*4882a593Smuzhiyundef add(args, config, basepath, workspace): 32*4882a593Smuzhiyun """Entry point for the devtool 'add' subcommand""" 33*4882a593Smuzhiyun import bb 34*4882a593Smuzhiyun import oe.recipeutils 35*4882a593Smuzhiyun 36*4882a593Smuzhiyun if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri: 37*4882a593Smuzhiyun raise argparse_oe.ArgumentUsageError('At least one of recipename, srctree, fetchuri or -f/--fetch must be specified', 'add') 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun # These are positional arguments, but because we're nice, allow 40*4882a593Smuzhiyun # specifying e.g. source tree without name, or fetch URI without name or 41*4882a593Smuzhiyun # source tree (if we can detect that that is what the user meant) 42*4882a593Smuzhiyun if scriptutils.is_src_url(args.recipename): 43*4882a593Smuzhiyun if not args.fetchuri: 44*4882a593Smuzhiyun if args.fetch: 45*4882a593Smuzhiyun raise DevtoolError('URI specified as positional argument as well as -f/--fetch') 46*4882a593Smuzhiyun args.fetchuri = args.recipename 47*4882a593Smuzhiyun args.recipename = '' 48*4882a593Smuzhiyun elif scriptutils.is_src_url(args.srctree): 49*4882a593Smuzhiyun if not args.fetchuri: 50*4882a593Smuzhiyun if args.fetch: 51*4882a593Smuzhiyun raise DevtoolError('URI specified as positional argument as well as -f/--fetch') 52*4882a593Smuzhiyun args.fetchuri = args.srctree 53*4882a593Smuzhiyun args.srctree = '' 54*4882a593Smuzhiyun elif args.recipename and not args.srctree: 55*4882a593Smuzhiyun if os.sep in args.recipename: 56*4882a593Smuzhiyun args.srctree = args.recipename 57*4882a593Smuzhiyun args.recipename = None 58*4882a593Smuzhiyun elif os.path.isdir(args.recipename): 59*4882a593Smuzhiyun logger.warning('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename) 60*4882a593Smuzhiyun 61*4882a593Smuzhiyun if not args.fetchuri: 62*4882a593Smuzhiyun if args.srcrev: 63*4882a593Smuzhiyun raise DevtoolError('The -S/--srcrev option is only valid when fetching from an SCM repository') 64*4882a593Smuzhiyun if args.srcbranch: 65*4882a593Smuzhiyun raise DevtoolError('The -B/--srcbranch option is only valid when fetching from an SCM repository') 66*4882a593Smuzhiyun 67*4882a593Smuzhiyun if args.srctree and os.path.isfile(args.srctree): 68*4882a593Smuzhiyun args.fetchuri = 'file://' + os.path.abspath(args.srctree) 69*4882a593Smuzhiyun args.srctree = '' 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun if args.fetch: 72*4882a593Smuzhiyun if args.fetchuri: 73*4882a593Smuzhiyun raise DevtoolError('URI specified as positional argument as well as -f/--fetch') 74*4882a593Smuzhiyun else: 75*4882a593Smuzhiyun logger.warning('-f/--fetch option is deprecated - you can now simply specify the URL to fetch as a positional argument instead') 76*4882a593Smuzhiyun args.fetchuri = args.fetch 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun if args.recipename: 79*4882a593Smuzhiyun if args.recipename in workspace: 80*4882a593Smuzhiyun raise DevtoolError("recipe %s is already in your workspace" % 81*4882a593Smuzhiyun args.recipename) 82*4882a593Smuzhiyun reason = oe.recipeutils.validate_pn(args.recipename) 83*4882a593Smuzhiyun if reason: 84*4882a593Smuzhiyun raise DevtoolError(reason) 85*4882a593Smuzhiyun 86*4882a593Smuzhiyun if args.srctree: 87*4882a593Smuzhiyun srctree = os.path.abspath(args.srctree) 88*4882a593Smuzhiyun srctreeparent = None 89*4882a593Smuzhiyun tmpsrcdir = None 90*4882a593Smuzhiyun else: 91*4882a593Smuzhiyun srctree = None 92*4882a593Smuzhiyun srctreeparent = get_default_srctree(config) 93*4882a593Smuzhiyun bb.utils.mkdirhier(srctreeparent) 94*4882a593Smuzhiyun tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent) 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun if srctree and os.path.exists(srctree): 97*4882a593Smuzhiyun if args.fetchuri: 98*4882a593Smuzhiyun if not os.path.isdir(srctree): 99*4882a593Smuzhiyun raise DevtoolError("Cannot fetch into source tree path %s as " 100*4882a593Smuzhiyun "it exists and is not a directory" % 101*4882a593Smuzhiyun srctree) 102*4882a593Smuzhiyun elif os.listdir(srctree): 103*4882a593Smuzhiyun raise DevtoolError("Cannot fetch into source tree path %s as " 104*4882a593Smuzhiyun "it already exists and is non-empty" % 105*4882a593Smuzhiyun srctree) 106*4882a593Smuzhiyun elif not args.fetchuri: 107*4882a593Smuzhiyun if args.srctree: 108*4882a593Smuzhiyun raise DevtoolError("Specified source tree %s could not be found" % 109*4882a593Smuzhiyun args.srctree) 110*4882a593Smuzhiyun elif srctree: 111*4882a593Smuzhiyun raise DevtoolError("No source tree exists at default path %s - " 112*4882a593Smuzhiyun "either create and populate this directory, " 113*4882a593Smuzhiyun "or specify a path to a source tree, or a " 114*4882a593Smuzhiyun "URI to fetch source from" % srctree) 115*4882a593Smuzhiyun else: 116*4882a593Smuzhiyun raise DevtoolError("You must either specify a source tree " 117*4882a593Smuzhiyun "or a URI to fetch source from") 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun if args.version: 120*4882a593Smuzhiyun if '_' in args.version or ' ' in args.version: 121*4882a593Smuzhiyun raise DevtoolError('Invalid version string "%s"' % args.version) 122*4882a593Smuzhiyun 123*4882a593Smuzhiyun if args.color == 'auto' and sys.stdout.isatty(): 124*4882a593Smuzhiyun color = 'always' 125*4882a593Smuzhiyun else: 126*4882a593Smuzhiyun color = args.color 127*4882a593Smuzhiyun extracmdopts = '' 128*4882a593Smuzhiyun if args.fetchuri: 129*4882a593Smuzhiyun source = args.fetchuri 130*4882a593Smuzhiyun if srctree: 131*4882a593Smuzhiyun extracmdopts += ' -x %s' % srctree 132*4882a593Smuzhiyun else: 133*4882a593Smuzhiyun extracmdopts += ' -x %s' % tmpsrcdir 134*4882a593Smuzhiyun else: 135*4882a593Smuzhiyun source = srctree 136*4882a593Smuzhiyun if args.recipename: 137*4882a593Smuzhiyun extracmdopts += ' -N %s' % args.recipename 138*4882a593Smuzhiyun if args.version: 139*4882a593Smuzhiyun extracmdopts += ' -V %s' % args.version 140*4882a593Smuzhiyun if args.binary: 141*4882a593Smuzhiyun extracmdopts += ' -b' 142*4882a593Smuzhiyun if args.also_native: 143*4882a593Smuzhiyun extracmdopts += ' --also-native' 144*4882a593Smuzhiyun if args.src_subdir: 145*4882a593Smuzhiyun extracmdopts += ' --src-subdir "%s"' % args.src_subdir 146*4882a593Smuzhiyun if args.autorev: 147*4882a593Smuzhiyun extracmdopts += ' -a' 148*4882a593Smuzhiyun if args.npm_dev: 149*4882a593Smuzhiyun extracmdopts += ' --npm-dev' 150*4882a593Smuzhiyun if args.mirrors: 151*4882a593Smuzhiyun extracmdopts += ' --mirrors' 152*4882a593Smuzhiyun if args.srcrev: 153*4882a593Smuzhiyun extracmdopts += ' --srcrev %s' % args.srcrev 154*4882a593Smuzhiyun if args.srcbranch: 155*4882a593Smuzhiyun extracmdopts += ' --srcbranch %s' % args.srcbranch 156*4882a593Smuzhiyun if args.provides: 157*4882a593Smuzhiyun extracmdopts += ' --provides %s' % args.provides 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun tempdir = tempfile.mkdtemp(prefix='devtool') 160*4882a593Smuzhiyun try: 161*4882a593Smuzhiyun try: 162*4882a593Smuzhiyun stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True) 163*4882a593Smuzhiyun except bb.process.ExecutionError as e: 164*4882a593Smuzhiyun if e.exitcode == 15: 165*4882a593Smuzhiyun raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line') 166*4882a593Smuzhiyun else: 167*4882a593Smuzhiyun raise DevtoolError('Command \'%s\' failed' % e.command) 168*4882a593Smuzhiyun 169*4882a593Smuzhiyun recipes = glob.glob(os.path.join(tempdir, '*.bb')) 170*4882a593Smuzhiyun if recipes: 171*4882a593Smuzhiyun recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0] 172*4882a593Smuzhiyun if recipename in workspace: 173*4882a593Smuzhiyun raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename) 174*4882a593Smuzhiyun recipedir = os.path.join(config.workspace_path, 'recipes', recipename) 175*4882a593Smuzhiyun bb.utils.mkdirhier(recipedir) 176*4882a593Smuzhiyun recipefile = os.path.join(recipedir, os.path.basename(recipes[0])) 177*4882a593Smuzhiyun appendfile = recipe_to_append(recipefile, config) 178*4882a593Smuzhiyun if os.path.exists(appendfile): 179*4882a593Smuzhiyun # This shouldn't be possible, but just in case 180*4882a593Smuzhiyun raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace') 181*4882a593Smuzhiyun if os.path.exists(recipefile): 182*4882a593Smuzhiyun raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile) 183*4882a593Smuzhiyun if tmpsrcdir: 184*4882a593Smuzhiyun srctree = os.path.join(srctreeparent, recipename) 185*4882a593Smuzhiyun if os.path.exists(tmpsrcdir): 186*4882a593Smuzhiyun if os.path.exists(srctree): 187*4882a593Smuzhiyun if os.path.isdir(srctree): 188*4882a593Smuzhiyun try: 189*4882a593Smuzhiyun os.rmdir(srctree) 190*4882a593Smuzhiyun except OSError as e: 191*4882a593Smuzhiyun if e.errno == errno.ENOTEMPTY: 192*4882a593Smuzhiyun raise DevtoolError('Source tree path %s already exists and is not empty' % srctree) 193*4882a593Smuzhiyun else: 194*4882a593Smuzhiyun raise 195*4882a593Smuzhiyun else: 196*4882a593Smuzhiyun raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree) 197*4882a593Smuzhiyun logger.info('Using default source tree path %s' % srctree) 198*4882a593Smuzhiyun shutil.move(tmpsrcdir, srctree) 199*4882a593Smuzhiyun else: 200*4882a593Smuzhiyun raise DevtoolError('Couldn\'t find source tree created by recipetool') 201*4882a593Smuzhiyun bb.utils.mkdirhier(recipedir) 202*4882a593Smuzhiyun shutil.move(recipes[0], recipefile) 203*4882a593Smuzhiyun # Move any additional files created by recipetool 204*4882a593Smuzhiyun for fn in os.listdir(tempdir): 205*4882a593Smuzhiyun shutil.move(os.path.join(tempdir, fn), recipedir) 206*4882a593Smuzhiyun else: 207*4882a593Smuzhiyun raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout)) 208*4882a593Smuzhiyun attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile)) 209*4882a593Smuzhiyun if os.path.exists(attic_recipe): 210*4882a593Smuzhiyun logger.warning('A modified recipe from a previous invocation exists in %s - you may wish to move this over the top of the new recipe if you had changes in it that you want to continue with' % attic_recipe) 211*4882a593Smuzhiyun finally: 212*4882a593Smuzhiyun if tmpsrcdir and os.path.exists(tmpsrcdir): 213*4882a593Smuzhiyun shutil.rmtree(tmpsrcdir) 214*4882a593Smuzhiyun shutil.rmtree(tempdir) 215*4882a593Smuzhiyun 216*4882a593Smuzhiyun for fn in os.listdir(recipedir): 217*4882a593Smuzhiyun _add_md5(config, recipename, os.path.join(recipedir, fn)) 218*4882a593Smuzhiyun 219*4882a593Smuzhiyun tinfoil = setup_tinfoil(config_only=True, basepath=basepath) 220*4882a593Smuzhiyun try: 221*4882a593Smuzhiyun try: 222*4882a593Smuzhiyun rd = tinfoil.parse_recipe_file(recipefile, False) 223*4882a593Smuzhiyun except Exception as e: 224*4882a593Smuzhiyun logger.error(str(e)) 225*4882a593Smuzhiyun rd = None 226*4882a593Smuzhiyun if not rd: 227*4882a593Smuzhiyun # Parsing failed. We just created this recipe and we shouldn't 228*4882a593Smuzhiyun # leave it in the workdir or it'll prevent bitbake from starting 229*4882a593Smuzhiyun movefn = '%s.parsefailed' % recipefile 230*4882a593Smuzhiyun logger.error('Parsing newly created recipe failed, moving recipe to %s for reference. If this looks to be caused by the recipe itself, please report this error.' % movefn) 231*4882a593Smuzhiyun shutil.move(recipefile, movefn) 232*4882a593Smuzhiyun return 1 233*4882a593Smuzhiyun 234*4882a593Smuzhiyun if args.fetchuri and not args.no_git: 235*4882a593Smuzhiyun setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data) 236*4882a593Smuzhiyun 237*4882a593Smuzhiyun initial_rev = None 238*4882a593Smuzhiyun if os.path.exists(os.path.join(srctree, '.git')): 239*4882a593Smuzhiyun (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree) 240*4882a593Smuzhiyun initial_rev = stdout.rstrip() 241*4882a593Smuzhiyun 242*4882a593Smuzhiyun if args.src_subdir: 243*4882a593Smuzhiyun srctree = os.path.join(srctree, args.src_subdir) 244*4882a593Smuzhiyun 245*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(appendfile)) 246*4882a593Smuzhiyun with open(appendfile, 'w') as f: 247*4882a593Smuzhiyun f.write('inherit externalsrc\n') 248*4882a593Smuzhiyun f.write('EXTERNALSRC = "%s"\n' % srctree) 249*4882a593Smuzhiyun 250*4882a593Smuzhiyun b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd) 251*4882a593Smuzhiyun if b_is_s: 252*4882a593Smuzhiyun f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree) 253*4882a593Smuzhiyun if initial_rev: 254*4882a593Smuzhiyun f.write('\n# initial_rev: %s\n' % initial_rev) 255*4882a593Smuzhiyun 256*4882a593Smuzhiyun if args.binary: 257*4882a593Smuzhiyun f.write('do_install:append() {\n') 258*4882a593Smuzhiyun f.write(' rm -rf ${D}/.git\n') 259*4882a593Smuzhiyun f.write(' rm -f ${D}/singletask.lock\n') 260*4882a593Smuzhiyun f.write('}\n') 261*4882a593Smuzhiyun 262*4882a593Smuzhiyun if bb.data.inherits_class('npm', rd): 263*4882a593Smuzhiyun f.write('python do_configure:append() {\n') 264*4882a593Smuzhiyun f.write(' pkgdir = d.getVar("NPM_PACKAGE")\n') 265*4882a593Smuzhiyun f.write(' lockfile = os.path.join(pkgdir, "singletask.lock")\n') 266*4882a593Smuzhiyun f.write(' bb.utils.remove(lockfile)\n') 267*4882a593Smuzhiyun f.write('}\n') 268*4882a593Smuzhiyun 269*4882a593Smuzhiyun # Check if the new layer provides recipes whose priorities have been 270*4882a593Smuzhiyun # overriden by PREFERRED_PROVIDER. 271*4882a593Smuzhiyun recipe_name = rd.getVar('PN') 272*4882a593Smuzhiyun provides = rd.getVar('PROVIDES') 273*4882a593Smuzhiyun # Search every item defined in PROVIDES 274*4882a593Smuzhiyun for recipe_provided in provides.split(): 275*4882a593Smuzhiyun preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided 276*4882a593Smuzhiyun current_pprovider = rd.getVar(preferred_provider) 277*4882a593Smuzhiyun if current_pprovider and current_pprovider != recipe_name: 278*4882a593Smuzhiyun if args.fixed_setup: 279*4882a593Smuzhiyun #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf 280*4882a593Smuzhiyun layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf") 281*4882a593Smuzhiyun with open(layerconf_file, 'a') as f: 282*4882a593Smuzhiyun f.write('%s = "%s"\n' % (preferred_provider, recipe_name)) 283*4882a593Smuzhiyun else: 284*4882a593Smuzhiyun logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider) 285*4882a593Smuzhiyun break 286*4882a593Smuzhiyun 287*4882a593Smuzhiyun _add_md5(config, recipename, appendfile) 288*4882a593Smuzhiyun 289*4882a593Smuzhiyun check_prerelease_version(rd.getVar('PV'), 'devtool add') 290*4882a593Smuzhiyun 291*4882a593Smuzhiyun logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile) 292*4882a593Smuzhiyun 293*4882a593Smuzhiyun finally: 294*4882a593Smuzhiyun tinfoil.shutdown() 295*4882a593Smuzhiyun 296*4882a593Smuzhiyun return 0 297*4882a593Smuzhiyun 298*4882a593Smuzhiyun 299*4882a593Smuzhiyundef _check_compatible_recipe(pn, d): 300*4882a593Smuzhiyun """Check if the recipe is supported by devtool""" 301*4882a593Smuzhiyun if pn == 'perf': 302*4882a593Smuzhiyun raise DevtoolError("The perf recipe does not actually check out " 303*4882a593Smuzhiyun "source and thus cannot be supported by this tool", 304*4882a593Smuzhiyun 4) 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'): 307*4882a593Smuzhiyun raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4) 308*4882a593Smuzhiyun 309*4882a593Smuzhiyun if bb.data.inherits_class('image', d): 310*4882a593Smuzhiyun raise DevtoolError("The %s recipe is an image, and therefore is not " 311*4882a593Smuzhiyun "supported by this tool" % pn, 4) 312*4882a593Smuzhiyun 313*4882a593Smuzhiyun if bb.data.inherits_class('populate_sdk', d): 314*4882a593Smuzhiyun raise DevtoolError("The %s recipe is an SDK, and therefore is not " 315*4882a593Smuzhiyun "supported by this tool" % pn, 4) 316*4882a593Smuzhiyun 317*4882a593Smuzhiyun if bb.data.inherits_class('packagegroup', d): 318*4882a593Smuzhiyun raise DevtoolError("The %s recipe is a packagegroup, and therefore is " 319*4882a593Smuzhiyun "not supported by this tool" % pn, 4) 320*4882a593Smuzhiyun 321*4882a593Smuzhiyun if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'): 322*4882a593Smuzhiyun # Not an incompatibility error per se, so we don't pass the error code 323*4882a593Smuzhiyun raise DevtoolError("externalsrc is currently enabled for the %s " 324*4882a593Smuzhiyun "recipe. This prevents the normal do_patch task " 325*4882a593Smuzhiyun "from working. You will need to disable this " 326*4882a593Smuzhiyun "first." % pn) 327*4882a593Smuzhiyun 328*4882a593Smuzhiyundef _dry_run_copy(src, dst, dry_run_outdir, base_outdir): 329*4882a593Smuzhiyun """Common function for copying a file to the dry run output directory""" 330*4882a593Smuzhiyun relpath = os.path.relpath(dst, base_outdir) 331*4882a593Smuzhiyun if relpath.startswith('..'): 332*4882a593Smuzhiyun raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst)) 333*4882a593Smuzhiyun dst = os.path.join(dry_run_outdir, relpath) 334*4882a593Smuzhiyun dst_d = os.path.dirname(dst) 335*4882a593Smuzhiyun if dst_d: 336*4882a593Smuzhiyun bb.utils.mkdirhier(dst_d) 337*4882a593Smuzhiyun # Don't overwrite existing files, otherwise in the case of an upgrade 338*4882a593Smuzhiyun # the dry-run written out recipe will be overwritten with an unmodified 339*4882a593Smuzhiyun # version 340*4882a593Smuzhiyun if not os.path.exists(dst): 341*4882a593Smuzhiyun shutil.copy(src, dst) 342*4882a593Smuzhiyun 343*4882a593Smuzhiyundef _move_file(src, dst, dry_run_outdir=None, base_outdir=None): 344*4882a593Smuzhiyun """Move a file. Creates all the directory components of destination path.""" 345*4882a593Smuzhiyun dry_run_suffix = ' (dry-run)' if dry_run_outdir else '' 346*4882a593Smuzhiyun logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix)) 347*4882a593Smuzhiyun if dry_run_outdir: 348*4882a593Smuzhiyun # We want to copy here, not move 349*4882a593Smuzhiyun _dry_run_copy(src, dst, dry_run_outdir, base_outdir) 350*4882a593Smuzhiyun else: 351*4882a593Smuzhiyun dst_d = os.path.dirname(dst) 352*4882a593Smuzhiyun if dst_d: 353*4882a593Smuzhiyun bb.utils.mkdirhier(dst_d) 354*4882a593Smuzhiyun shutil.move(src, dst) 355*4882a593Smuzhiyun 356*4882a593Smuzhiyundef _copy_file(src, dst, dry_run_outdir=None, base_outdir=None): 357*4882a593Smuzhiyun """Copy a file. Creates all the directory components of destination path.""" 358*4882a593Smuzhiyun dry_run_suffix = ' (dry-run)' if dry_run_outdir else '' 359*4882a593Smuzhiyun logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix)) 360*4882a593Smuzhiyun if dry_run_outdir: 361*4882a593Smuzhiyun _dry_run_copy(src, dst, dry_run_outdir, base_outdir) 362*4882a593Smuzhiyun else: 363*4882a593Smuzhiyun dst_d = os.path.dirname(dst) 364*4882a593Smuzhiyun if dst_d: 365*4882a593Smuzhiyun bb.utils.mkdirhier(dst_d) 366*4882a593Smuzhiyun shutil.copy(src, dst) 367*4882a593Smuzhiyun 368*4882a593Smuzhiyundef _git_ls_tree(repodir, treeish='HEAD', recursive=False): 369*4882a593Smuzhiyun """List contents of a git treeish""" 370*4882a593Smuzhiyun import bb 371*4882a593Smuzhiyun cmd = ['git', 'ls-tree', '-z', treeish] 372*4882a593Smuzhiyun if recursive: 373*4882a593Smuzhiyun cmd.append('-r') 374*4882a593Smuzhiyun out, _ = bb.process.run(cmd, cwd=repodir) 375*4882a593Smuzhiyun ret = {} 376*4882a593Smuzhiyun if out: 377*4882a593Smuzhiyun for line in out.split('\0'): 378*4882a593Smuzhiyun if line: 379*4882a593Smuzhiyun split = line.split(None, 4) 380*4882a593Smuzhiyun ret[split[3]] = split[0:3] 381*4882a593Smuzhiyun return ret 382*4882a593Smuzhiyun 383*4882a593Smuzhiyundef _git_exclude_path(srctree, path): 384*4882a593Smuzhiyun """Return pathspec (list of paths) that excludes certain path""" 385*4882a593Smuzhiyun # NOTE: "Filtering out" files/paths in this way is not entirely reliable - 386*4882a593Smuzhiyun # we don't catch files that are deleted, for example. A more reliable way 387*4882a593Smuzhiyun # to implement this would be to use "negative pathspecs" which were 388*4882a593Smuzhiyun # introduced in Git v1.9.0. Revisit this when/if the required Git version 389*4882a593Smuzhiyun # becomes greater than that. 390*4882a593Smuzhiyun path = os.path.normpath(path) 391*4882a593Smuzhiyun recurse = True if len(path.split(os.path.sep)) > 1 else False 392*4882a593Smuzhiyun git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys()) 393*4882a593Smuzhiyun if path in git_files: 394*4882a593Smuzhiyun git_files.remove(path) 395*4882a593Smuzhiyun return git_files 396*4882a593Smuzhiyun else: 397*4882a593Smuzhiyun return ['.'] 398*4882a593Smuzhiyun 399*4882a593Smuzhiyundef _ls_tree(directory): 400*4882a593Smuzhiyun """Recursive listing of files in a directory""" 401*4882a593Smuzhiyun ret = [] 402*4882a593Smuzhiyun for root, dirs, files in os.walk(directory): 403*4882a593Smuzhiyun ret.extend([os.path.relpath(os.path.join(root, fname), directory) for 404*4882a593Smuzhiyun fname in files]) 405*4882a593Smuzhiyun return ret 406*4882a593Smuzhiyun 407*4882a593Smuzhiyun 408*4882a593Smuzhiyundef extract(args, config, basepath, workspace): 409*4882a593Smuzhiyun """Entry point for the devtool 'extract' subcommand""" 410*4882a593Smuzhiyun import bb 411*4882a593Smuzhiyun 412*4882a593Smuzhiyun tinfoil = setup_tinfoil(basepath=basepath, tracking=True) 413*4882a593Smuzhiyun if not tinfoil: 414*4882a593Smuzhiyun # Error already shown 415*4882a593Smuzhiyun return 1 416*4882a593Smuzhiyun try: 417*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, args.recipename, True) 418*4882a593Smuzhiyun if not rd: 419*4882a593Smuzhiyun return 1 420*4882a593Smuzhiyun 421*4882a593Smuzhiyun srctree = os.path.abspath(args.srctree) 422*4882a593Smuzhiyun initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides) 423*4882a593Smuzhiyun logger.info('Source tree extracted to %s' % srctree) 424*4882a593Smuzhiyun 425*4882a593Smuzhiyun if initial_rev: 426*4882a593Smuzhiyun return 0 427*4882a593Smuzhiyun else: 428*4882a593Smuzhiyun return 1 429*4882a593Smuzhiyun finally: 430*4882a593Smuzhiyun tinfoil.shutdown() 431*4882a593Smuzhiyun 432*4882a593Smuzhiyundef sync(args, config, basepath, workspace): 433*4882a593Smuzhiyun """Entry point for the devtool 'sync' subcommand""" 434*4882a593Smuzhiyun import bb 435*4882a593Smuzhiyun 436*4882a593Smuzhiyun tinfoil = setup_tinfoil(basepath=basepath, tracking=True) 437*4882a593Smuzhiyun if not tinfoil: 438*4882a593Smuzhiyun # Error already shown 439*4882a593Smuzhiyun return 1 440*4882a593Smuzhiyun try: 441*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, args.recipename, True) 442*4882a593Smuzhiyun if not rd: 443*4882a593Smuzhiyun return 1 444*4882a593Smuzhiyun 445*4882a593Smuzhiyun srctree = os.path.abspath(args.srctree) 446*4882a593Smuzhiyun initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, True, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=True) 447*4882a593Smuzhiyun logger.info('Source tree %s synchronized' % srctree) 448*4882a593Smuzhiyun 449*4882a593Smuzhiyun if initial_rev: 450*4882a593Smuzhiyun return 0 451*4882a593Smuzhiyun else: 452*4882a593Smuzhiyun return 1 453*4882a593Smuzhiyun finally: 454*4882a593Smuzhiyun tinfoil.shutdown() 455*4882a593Smuzhiyun 456*4882a593Smuzhiyundef symlink_oelocal_files_srctree(rd,srctree): 457*4882a593Smuzhiyun import oe.patch 458*4882a593Smuzhiyun if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')): 459*4882a593Smuzhiyun # If recipe extracts to ${WORKDIR}, symlink the files into the srctree 460*4882a593Smuzhiyun # (otherwise the recipe won't build as expected) 461*4882a593Smuzhiyun local_files_dir = os.path.join(srctree, 'oe-local-files') 462*4882a593Smuzhiyun addfiles = [] 463*4882a593Smuzhiyun for root, _, files in os.walk(local_files_dir): 464*4882a593Smuzhiyun relpth = os.path.relpath(root, local_files_dir) 465*4882a593Smuzhiyun if relpth != '.': 466*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.join(srctree, relpth)) 467*4882a593Smuzhiyun for fn in files: 468*4882a593Smuzhiyun if fn == '.gitignore': 469*4882a593Smuzhiyun continue 470*4882a593Smuzhiyun destpth = os.path.join(srctree, relpth, fn) 471*4882a593Smuzhiyun if os.path.exists(destpth): 472*4882a593Smuzhiyun os.unlink(destpth) 473*4882a593Smuzhiyun if relpth != '.': 474*4882a593Smuzhiyun back_relpth = os.path.relpath(local_files_dir, root) 475*4882a593Smuzhiyun os.symlink('%s/oe-local-files/%s/%s' % (back_relpth, relpth, fn), destpth) 476*4882a593Smuzhiyun else: 477*4882a593Smuzhiyun os.symlink('oe-local-files/%s' % fn, destpth) 478*4882a593Smuzhiyun addfiles.append(os.path.join(relpth, fn)) 479*4882a593Smuzhiyun if addfiles: 480*4882a593Smuzhiyun bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree) 481*4882a593Smuzhiyun useroptions = [] 482*4882a593Smuzhiyun oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=rd) 483*4882a593Smuzhiyun bb.process.run('git %s commit -m "Committing local file symlinks\n\n%s"' % (' '.join(useroptions), oe.patch.GitApplyTree.ignore_commit_prefix), cwd=srctree) 484*4882a593Smuzhiyun 485*4882a593Smuzhiyun 486*4882a593Smuzhiyundef _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False): 487*4882a593Smuzhiyun """Extract sources of a recipe""" 488*4882a593Smuzhiyun import oe.recipeutils 489*4882a593Smuzhiyun import oe.patch 490*4882a593Smuzhiyun import oe.path 491*4882a593Smuzhiyun 492*4882a593Smuzhiyun pn = d.getVar('PN') 493*4882a593Smuzhiyun 494*4882a593Smuzhiyun _check_compatible_recipe(pn, d) 495*4882a593Smuzhiyun 496*4882a593Smuzhiyun if sync: 497*4882a593Smuzhiyun if not os.path.exists(srctree): 498*4882a593Smuzhiyun raise DevtoolError("output path %s does not exist" % srctree) 499*4882a593Smuzhiyun else: 500*4882a593Smuzhiyun if os.path.exists(srctree): 501*4882a593Smuzhiyun if not os.path.isdir(srctree): 502*4882a593Smuzhiyun raise DevtoolError("output path %s exists and is not a directory" % 503*4882a593Smuzhiyun srctree) 504*4882a593Smuzhiyun elif os.listdir(srctree): 505*4882a593Smuzhiyun raise DevtoolError("output path %s already exists and is " 506*4882a593Smuzhiyun "non-empty" % srctree) 507*4882a593Smuzhiyun 508*4882a593Smuzhiyun if 'noexec' in (d.getVarFlags('do_unpack', False) or []): 509*4882a593Smuzhiyun raise DevtoolError("The %s recipe has do_unpack disabled, unable to " 510*4882a593Smuzhiyun "extract source" % pn, 4) 511*4882a593Smuzhiyun 512*4882a593Smuzhiyun if not sync: 513*4882a593Smuzhiyun # Prepare for shutil.move later on 514*4882a593Smuzhiyun bb.utils.mkdirhier(srctree) 515*4882a593Smuzhiyun os.rmdir(srctree) 516*4882a593Smuzhiyun 517*4882a593Smuzhiyun extra_overrides = [] 518*4882a593Smuzhiyun if not no_overrides: 519*4882a593Smuzhiyun history = d.varhistory.variable('SRC_URI') 520*4882a593Smuzhiyun for event in history: 521*4882a593Smuzhiyun if not 'flag' in event: 522*4882a593Smuzhiyun if event['op'].startswith((':append[', ':prepend[')): 523*4882a593Smuzhiyun override = event['op'].split('[')[1].split(']')[0] 524*4882a593Smuzhiyun if not override.startswith('pn-'): 525*4882a593Smuzhiyun extra_overrides.append(override) 526*4882a593Smuzhiyun # We want to remove duplicate overrides. If a recipe had multiple 527*4882a593Smuzhiyun # SRC_URI_override += values it would cause mulitple instances of 528*4882a593Smuzhiyun # overrides. This doesn't play nicely with things like creating a 529*4882a593Smuzhiyun # branch for every instance of DEVTOOL_EXTRA_OVERRIDES. 530*4882a593Smuzhiyun extra_overrides = list(set(extra_overrides)) 531*4882a593Smuzhiyun if extra_overrides: 532*4882a593Smuzhiyun logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these') 533*4882a593Smuzhiyun 534*4882a593Smuzhiyun initial_rev = None 535*4882a593Smuzhiyun 536*4882a593Smuzhiyun recipefile = d.getVar('FILE') 537*4882a593Smuzhiyun appendfile = recipe_to_append(recipefile, config) 538*4882a593Smuzhiyun is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d) 539*4882a593Smuzhiyun 540*4882a593Smuzhiyun # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary 541*4882a593Smuzhiyun # directory so that: 542*4882a593Smuzhiyun # (a) we pick up all files that get unpacked to the WORKDIR, and 543*4882a593Smuzhiyun # (b) we don't disturb the existing build 544*4882a593Smuzhiyun # However, with recipe-specific sysroots the sysroots for the recipe 545*4882a593Smuzhiyun # will be prepared under WORKDIR, and if we used the system temporary 546*4882a593Smuzhiyun # directory (i.e. usually /tmp) as used by mkdtemp by default, then 547*4882a593Smuzhiyun # our attempts to hardlink files into the recipe-specific sysroots 548*4882a593Smuzhiyun # will fail on systems where /tmp is a different filesystem, and it 549*4882a593Smuzhiyun # would have to fall back to copying the files which is a waste of 550*4882a593Smuzhiyun # time. Put the temp directory under the WORKDIR to prevent that from 551*4882a593Smuzhiyun # being a problem. 552*4882a593Smuzhiyun tempbasedir = d.getVar('WORKDIR') 553*4882a593Smuzhiyun bb.utils.mkdirhier(tempbasedir) 554*4882a593Smuzhiyun tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir) 555*4882a593Smuzhiyun try: 556*4882a593Smuzhiyun tinfoil.logger.setLevel(logging.WARNING) 557*4882a593Smuzhiyun 558*4882a593Smuzhiyun # FIXME this results in a cache reload under control of tinfoil, which is fine 559*4882a593Smuzhiyun # except we don't get the knotty progress bar 560*4882a593Smuzhiyun 561*4882a593Smuzhiyun if os.path.exists(appendfile): 562*4882a593Smuzhiyun appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak') 563*4882a593Smuzhiyun shutil.copyfile(appendfile, appendbackup) 564*4882a593Smuzhiyun else: 565*4882a593Smuzhiyun appendbackup = None 566*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(appendfile)) 567*4882a593Smuzhiyun logger.debug('writing append file %s' % appendfile) 568*4882a593Smuzhiyun with open(appendfile, 'a') as f: 569*4882a593Smuzhiyun f.write('###--- _extract_source\n') 570*4882a593Smuzhiyun f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir) 571*4882a593Smuzhiyun f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch) 572*4882a593Smuzhiyun if not is_kernel_yocto: 573*4882a593Smuzhiyun f.write('PATCHTOOL = "git"\n') 574*4882a593Smuzhiyun f.write('PATCH_COMMIT_FUNCTIONS = "1"\n') 575*4882a593Smuzhiyun if extra_overrides: 576*4882a593Smuzhiyun f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides)) 577*4882a593Smuzhiyun f.write('inherit devtool-source\n') 578*4882a593Smuzhiyun f.write('###--- _extract_source\n') 579*4882a593Smuzhiyun 580*4882a593Smuzhiyun update_unlockedsigs(basepath, workspace, fixed_setup, [pn]) 581*4882a593Smuzhiyun 582*4882a593Smuzhiyun sstate_manifests = d.getVar('SSTATE_MANIFESTS') 583*4882a593Smuzhiyun bb.utils.mkdirhier(sstate_manifests) 584*4882a593Smuzhiyun preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps') 585*4882a593Smuzhiyun with open(preservestampfile, 'w') as f: 586*4882a593Smuzhiyun f.write(d.getVar('STAMP')) 587*4882a593Smuzhiyun try: 588*4882a593Smuzhiyun if is_kernel_yocto: 589*4882a593Smuzhiyun # We need to generate the kernel config 590*4882a593Smuzhiyun task = 'do_configure' 591*4882a593Smuzhiyun else: 592*4882a593Smuzhiyun task = 'do_patch' 593*4882a593Smuzhiyun 594*4882a593Smuzhiyun if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []): 595*4882a593Smuzhiyun logger.info('The %s recipe has %s disabled. Running only ' 596*4882a593Smuzhiyun 'do_configure task dependencies' % (pn, task)) 597*4882a593Smuzhiyun 598*4882a593Smuzhiyun if 'depends' in d.getVarFlags('do_configure', False): 599*4882a593Smuzhiyun pn = d.getVarFlags('do_configure', False)['depends'] 600*4882a593Smuzhiyun pn = pn.replace('${PV}', d.getVar('PV')) 601*4882a593Smuzhiyun pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP')) 602*4882a593Smuzhiyun task = None 603*4882a593Smuzhiyun 604*4882a593Smuzhiyun # Run the fetch + unpack tasks 605*4882a593Smuzhiyun res = tinfoil.build_targets(pn, 606*4882a593Smuzhiyun task, 607*4882a593Smuzhiyun handle_events=True) 608*4882a593Smuzhiyun finally: 609*4882a593Smuzhiyun if os.path.exists(preservestampfile): 610*4882a593Smuzhiyun os.remove(preservestampfile) 611*4882a593Smuzhiyun 612*4882a593Smuzhiyun if not res: 613*4882a593Smuzhiyun raise DevtoolError('Extracting source for %s failed' % pn) 614*4882a593Smuzhiyun 615*4882a593Smuzhiyun if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])): 616*4882a593Smuzhiyun workshareddir = d.getVar('S') 617*4882a593Smuzhiyun if os.path.islink(srctree): 618*4882a593Smuzhiyun os.unlink(srctree) 619*4882a593Smuzhiyun 620*4882a593Smuzhiyun os.symlink(workshareddir, srctree) 621*4882a593Smuzhiyun 622*4882a593Smuzhiyun # The initial_rev file is created in devtool_post_unpack function that will not be executed if 623*4882a593Smuzhiyun # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful 624*4882a593Smuzhiyun return True, True 625*4882a593Smuzhiyun 626*4882a593Smuzhiyun try: 627*4882a593Smuzhiyun with open(os.path.join(tempdir, 'initial_rev'), 'r') as f: 628*4882a593Smuzhiyun initial_rev = f.read() 629*4882a593Smuzhiyun 630*4882a593Smuzhiyun with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f: 631*4882a593Smuzhiyun srcsubdir = f.read() 632*4882a593Smuzhiyun except FileNotFoundError as e: 633*4882a593Smuzhiyun raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e)) 634*4882a593Smuzhiyun srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir')) 635*4882a593Smuzhiyun 636*4882a593Smuzhiyun # Check if work-shared is empty, if yes 637*4882a593Smuzhiyun # find source and copy to work-shared 638*4882a593Smuzhiyun if is_kernel_yocto: 639*4882a593Smuzhiyun workshareddir = d.getVar('STAGING_KERNEL_DIR') 640*4882a593Smuzhiyun staging_kerVer = get_staging_kver(workshareddir) 641*4882a593Smuzhiyun kernelVersion = d.getVar('LINUX_VERSION') 642*4882a593Smuzhiyun 643*4882a593Smuzhiyun # handle dangling symbolic link in work-shared: 644*4882a593Smuzhiyun if os.path.islink(workshareddir): 645*4882a593Smuzhiyun os.unlink(workshareddir) 646*4882a593Smuzhiyun 647*4882a593Smuzhiyun if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer): 648*4882a593Smuzhiyun shutil.rmtree(workshareddir) 649*4882a593Smuzhiyun oe.path.copyhardlinktree(srcsubdir,workshareddir) 650*4882a593Smuzhiyun elif not os.path.exists(workshareddir): 651*4882a593Smuzhiyun oe.path.copyhardlinktree(srcsubdir,workshareddir) 652*4882a593Smuzhiyun 653*4882a593Smuzhiyun tempdir_localdir = os.path.join(tempdir, 'oe-local-files') 654*4882a593Smuzhiyun srctree_localdir = os.path.join(srctree, 'oe-local-files') 655*4882a593Smuzhiyun 656*4882a593Smuzhiyun if sync: 657*4882a593Smuzhiyun bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree) 658*4882a593Smuzhiyun 659*4882a593Smuzhiyun # Move oe-local-files directory to srctree 660*4882a593Smuzhiyun # As the oe-local-files is not part of the constructed git tree, 661*4882a593Smuzhiyun # remove them directly during the synchrounizating might surprise 662*4882a593Smuzhiyun # the users. Instead, we move it to oe-local-files.bak and remind 663*4882a593Smuzhiyun # user in the log message. 664*4882a593Smuzhiyun if os.path.exists(srctree_localdir + '.bak'): 665*4882a593Smuzhiyun shutil.rmtree(srctree_localdir, srctree_localdir + '.bak') 666*4882a593Smuzhiyun 667*4882a593Smuzhiyun if os.path.exists(srctree_localdir): 668*4882a593Smuzhiyun logger.info('Backing up current local file directory %s' % srctree_localdir) 669*4882a593Smuzhiyun shutil.move(srctree_localdir, srctree_localdir + '.bak') 670*4882a593Smuzhiyun 671*4882a593Smuzhiyun if os.path.exists(tempdir_localdir): 672*4882a593Smuzhiyun logger.info('Syncing local source files to srctree...') 673*4882a593Smuzhiyun shutil.copytree(tempdir_localdir, srctree_localdir) 674*4882a593Smuzhiyun else: 675*4882a593Smuzhiyun # Move oe-local-files directory to srctree 676*4882a593Smuzhiyun if os.path.exists(tempdir_localdir): 677*4882a593Smuzhiyun logger.info('Adding local source files to srctree...') 678*4882a593Smuzhiyun shutil.move(tempdir_localdir, srcsubdir) 679*4882a593Smuzhiyun 680*4882a593Smuzhiyun shutil.move(srcsubdir, srctree) 681*4882a593Smuzhiyun symlink_oelocal_files_srctree(d,srctree) 682*4882a593Smuzhiyun 683*4882a593Smuzhiyun if is_kernel_yocto: 684*4882a593Smuzhiyun logger.info('Copying kernel config to srctree') 685*4882a593Smuzhiyun shutil.copy2(os.path.join(tempdir, '.config'), srctree) 686*4882a593Smuzhiyun 687*4882a593Smuzhiyun finally: 688*4882a593Smuzhiyun if appendbackup: 689*4882a593Smuzhiyun shutil.copyfile(appendbackup, appendfile) 690*4882a593Smuzhiyun elif os.path.exists(appendfile): 691*4882a593Smuzhiyun os.remove(appendfile) 692*4882a593Smuzhiyun if keep_temp: 693*4882a593Smuzhiyun logger.info('Preserving temporary directory %s' % tempdir) 694*4882a593Smuzhiyun else: 695*4882a593Smuzhiyun shutil.rmtree(tempdir) 696*4882a593Smuzhiyun return initial_rev, srcsubdir_rel 697*4882a593Smuzhiyun 698*4882a593Smuzhiyundef _add_md5(config, recipename, filename): 699*4882a593Smuzhiyun """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace""" 700*4882a593Smuzhiyun import bb.utils 701*4882a593Smuzhiyun 702*4882a593Smuzhiyun def addfile(fn): 703*4882a593Smuzhiyun md5 = bb.utils.md5_file(fn) 704*4882a593Smuzhiyun with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f: 705*4882a593Smuzhiyun md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5) 706*4882a593Smuzhiyun f.seek(0, os.SEEK_SET) 707*4882a593Smuzhiyun if not md5_str in f.read(): 708*4882a593Smuzhiyun f.write(md5_str) 709*4882a593Smuzhiyun 710*4882a593Smuzhiyun if os.path.isdir(filename): 711*4882a593Smuzhiyun for root, _, files in os.walk(filename): 712*4882a593Smuzhiyun for f in files: 713*4882a593Smuzhiyun addfile(os.path.join(root, f)) 714*4882a593Smuzhiyun else: 715*4882a593Smuzhiyun addfile(filename) 716*4882a593Smuzhiyun 717*4882a593Smuzhiyundef _check_preserve(config, recipename): 718*4882a593Smuzhiyun """Check if a file was manually changed and needs to be saved in 'attic' 719*4882a593Smuzhiyun directory""" 720*4882a593Smuzhiyun import bb.utils 721*4882a593Smuzhiyun origfile = os.path.join(config.workspace_path, '.devtool_md5') 722*4882a593Smuzhiyun newfile = os.path.join(config.workspace_path, '.devtool_md5_new') 723*4882a593Smuzhiyun preservepath = os.path.join(config.workspace_path, 'attic', recipename) 724*4882a593Smuzhiyun with open(origfile, 'r') as f: 725*4882a593Smuzhiyun with open(newfile, 'w') as tf: 726*4882a593Smuzhiyun for line in f.readlines(): 727*4882a593Smuzhiyun splitline = line.rstrip().split('|') 728*4882a593Smuzhiyun if splitline[0] == recipename: 729*4882a593Smuzhiyun removefile = os.path.join(config.workspace_path, splitline[1]) 730*4882a593Smuzhiyun try: 731*4882a593Smuzhiyun md5 = bb.utils.md5_file(removefile) 732*4882a593Smuzhiyun except IOError as err: 733*4882a593Smuzhiyun if err.errno == 2: 734*4882a593Smuzhiyun # File no longer exists, skip it 735*4882a593Smuzhiyun continue 736*4882a593Smuzhiyun else: 737*4882a593Smuzhiyun raise 738*4882a593Smuzhiyun if splitline[2] != md5: 739*4882a593Smuzhiyun bb.utils.mkdirhier(preservepath) 740*4882a593Smuzhiyun preservefile = os.path.basename(removefile) 741*4882a593Smuzhiyun logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath)) 742*4882a593Smuzhiyun shutil.move(removefile, os.path.join(preservepath, preservefile)) 743*4882a593Smuzhiyun else: 744*4882a593Smuzhiyun os.remove(removefile) 745*4882a593Smuzhiyun else: 746*4882a593Smuzhiyun tf.write(line) 747*4882a593Smuzhiyun bb.utils.rename(newfile, origfile) 748*4882a593Smuzhiyun 749*4882a593Smuzhiyundef get_staging_kver(srcdir): 750*4882a593Smuzhiyun # Kernel version from work-shared 751*4882a593Smuzhiyun kerver = [] 752*4882a593Smuzhiyun staging_kerVer="" 753*4882a593Smuzhiyun if os.path.exists(srcdir) and os.listdir(srcdir): 754*4882a593Smuzhiyun with open(os.path.join(srcdir,"Makefile")) as f: 755*4882a593Smuzhiyun version = [next(f) for x in range(5)][1:4] 756*4882a593Smuzhiyun for word in version: 757*4882a593Smuzhiyun kerver.append(word.split('= ')[1].split('\n')[0]) 758*4882a593Smuzhiyun staging_kerVer = ".".join(kerver) 759*4882a593Smuzhiyun return staging_kerVer 760*4882a593Smuzhiyun 761*4882a593Smuzhiyundef get_staging_kbranch(srcdir): 762*4882a593Smuzhiyun staging_kbranch = "" 763*4882a593Smuzhiyun if os.path.exists(srcdir) and os.listdir(srcdir): 764*4882a593Smuzhiyun (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir) 765*4882a593Smuzhiyun staging_kbranch = "".join(branch.split('\n')[0]) 766*4882a593Smuzhiyun return staging_kbranch 767*4882a593Smuzhiyun 768*4882a593Smuzhiyundef get_real_srctree(srctree, s, workdir): 769*4882a593Smuzhiyun # Check that recipe isn't using a shared workdir 770*4882a593Smuzhiyun s = os.path.abspath(s) 771*4882a593Smuzhiyun workdir = os.path.abspath(workdir) 772*4882a593Smuzhiyun if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir: 773*4882a593Smuzhiyun # Handle if S is set to a subdirectory of the source 774*4882a593Smuzhiyun srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1] 775*4882a593Smuzhiyun srctree = os.path.join(srctree, srcsubdir) 776*4882a593Smuzhiyun return srctree 777*4882a593Smuzhiyun 778*4882a593Smuzhiyundef modify(args, config, basepath, workspace): 779*4882a593Smuzhiyun """Entry point for the devtool 'modify' subcommand""" 780*4882a593Smuzhiyun import bb 781*4882a593Smuzhiyun import oe.recipeutils 782*4882a593Smuzhiyun import oe.patch 783*4882a593Smuzhiyun import oe.path 784*4882a593Smuzhiyun 785*4882a593Smuzhiyun if args.recipename in workspace: 786*4882a593Smuzhiyun raise DevtoolError("recipe %s is already in your workspace" % 787*4882a593Smuzhiyun args.recipename) 788*4882a593Smuzhiyun 789*4882a593Smuzhiyun tinfoil = setup_tinfoil(basepath=basepath, tracking=True) 790*4882a593Smuzhiyun try: 791*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, args.recipename, True) 792*4882a593Smuzhiyun if not rd: 793*4882a593Smuzhiyun return 1 794*4882a593Smuzhiyun 795*4882a593Smuzhiyun pn = rd.getVar('PN') 796*4882a593Smuzhiyun if pn != args.recipename: 797*4882a593Smuzhiyun logger.info('Mapping %s to %s' % (args.recipename, pn)) 798*4882a593Smuzhiyun if pn in workspace: 799*4882a593Smuzhiyun raise DevtoolError("recipe %s is already in your workspace" % 800*4882a593Smuzhiyun pn) 801*4882a593Smuzhiyun 802*4882a593Smuzhiyun if args.srctree: 803*4882a593Smuzhiyun srctree = os.path.abspath(args.srctree) 804*4882a593Smuzhiyun else: 805*4882a593Smuzhiyun srctree = get_default_srctree(config, pn) 806*4882a593Smuzhiyun 807*4882a593Smuzhiyun if args.no_extract and not os.path.isdir(srctree): 808*4882a593Smuzhiyun raise DevtoolError("--no-extract specified and source path %s does " 809*4882a593Smuzhiyun "not exist or is not a directory" % 810*4882a593Smuzhiyun srctree) 811*4882a593Smuzhiyun 812*4882a593Smuzhiyun recipefile = rd.getVar('FILE') 813*4882a593Smuzhiyun appendfile = recipe_to_append(recipefile, config, args.wildcard) 814*4882a593Smuzhiyun if os.path.exists(appendfile): 815*4882a593Smuzhiyun raise DevtoolError("Another variant of recipe %s is already in your " 816*4882a593Smuzhiyun "workspace (only one variant of a recipe can " 817*4882a593Smuzhiyun "currently be worked on at once)" 818*4882a593Smuzhiyun % pn) 819*4882a593Smuzhiyun 820*4882a593Smuzhiyun _check_compatible_recipe(pn, rd) 821*4882a593Smuzhiyun 822*4882a593Smuzhiyun initial_rev = None 823*4882a593Smuzhiyun commits = [] 824*4882a593Smuzhiyun check_commits = False 825*4882a593Smuzhiyun 826*4882a593Smuzhiyun if bb.data.inherits_class('kernel-yocto', rd): 827*4882a593Smuzhiyun # Current set kernel version 828*4882a593Smuzhiyun kernelVersion = rd.getVar('LINUX_VERSION') 829*4882a593Smuzhiyun srcdir = rd.getVar('STAGING_KERNEL_DIR') 830*4882a593Smuzhiyun kbranch = rd.getVar('KBRANCH') 831*4882a593Smuzhiyun 832*4882a593Smuzhiyun staging_kerVer = get_staging_kver(srcdir) 833*4882a593Smuzhiyun staging_kbranch = get_staging_kbranch(srcdir) 834*4882a593Smuzhiyun if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch): 835*4882a593Smuzhiyun oe.path.copyhardlinktree(srcdir,srctree) 836*4882a593Smuzhiyun workdir = rd.getVar('WORKDIR') 837*4882a593Smuzhiyun srcsubdir = rd.getVar('S') 838*4882a593Smuzhiyun localfilesdir = os.path.join(srctree,'oe-local-files') 839*4882a593Smuzhiyun # Move local source files into separate subdir 840*4882a593Smuzhiyun recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)] 841*4882a593Smuzhiyun local_files = oe.recipeutils.get_recipe_local_files(rd) 842*4882a593Smuzhiyun 843*4882a593Smuzhiyun for key in local_files.copy(): 844*4882a593Smuzhiyun if key.endswith('scc'): 845*4882a593Smuzhiyun sccfile = open(local_files[key], 'r') 846*4882a593Smuzhiyun for l in sccfile: 847*4882a593Smuzhiyun line = l.split() 848*4882a593Smuzhiyun if line and line[0] in ('kconf', 'patch'): 849*4882a593Smuzhiyun cfg = os.path.join(os.path.dirname(local_files[key]), line[-1]) 850*4882a593Smuzhiyun if not cfg in local_files.values(): 851*4882a593Smuzhiyun local_files[line[-1]] = cfg 852*4882a593Smuzhiyun shutil.copy2(cfg, workdir) 853*4882a593Smuzhiyun sccfile.close() 854*4882a593Smuzhiyun 855*4882a593Smuzhiyun # Ignore local files with subdir={BP} 856*4882a593Smuzhiyun srcabspath = os.path.abspath(srcsubdir) 857*4882a593Smuzhiyun local_files = [fname for fname in local_files if os.path.exists(os.path.join(workdir, fname)) and (srcabspath == workdir or not os.path.join(workdir, fname).startswith(srcabspath + os.sep))] 858*4882a593Smuzhiyun if local_files: 859*4882a593Smuzhiyun for fname in local_files: 860*4882a593Smuzhiyun _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname)) 861*4882a593Smuzhiyun with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f: 862*4882a593Smuzhiyun f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n') 863*4882a593Smuzhiyun 864*4882a593Smuzhiyun symlink_oelocal_files_srctree(rd,srctree) 865*4882a593Smuzhiyun 866*4882a593Smuzhiyun task = 'do_configure' 867*4882a593Smuzhiyun res = tinfoil.build_targets(pn, task, handle_events=True) 868*4882a593Smuzhiyun 869*4882a593Smuzhiyun # Copy .config to workspace 870*4882a593Smuzhiyun kconfpath = rd.getVar('B') 871*4882a593Smuzhiyun logger.info('Copying kernel config to workspace') 872*4882a593Smuzhiyun shutil.copy2(os.path.join(kconfpath, '.config'),srctree) 873*4882a593Smuzhiyun 874*4882a593Smuzhiyun # Set this to true, we still need to get initial_rev 875*4882a593Smuzhiyun # by parsing the git repo 876*4882a593Smuzhiyun args.no_extract = True 877*4882a593Smuzhiyun 878*4882a593Smuzhiyun if not args.no_extract: 879*4882a593Smuzhiyun initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides) 880*4882a593Smuzhiyun if not initial_rev: 881*4882a593Smuzhiyun return 1 882*4882a593Smuzhiyun logger.info('Source tree extracted to %s' % srctree) 883*4882a593Smuzhiyun if os.path.exists(os.path.join(srctree, '.git')): 884*4882a593Smuzhiyun # Get list of commits since this revision 885*4882a593Smuzhiyun (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree) 886*4882a593Smuzhiyun commits = stdout.split() 887*4882a593Smuzhiyun check_commits = True 888*4882a593Smuzhiyun else: 889*4882a593Smuzhiyun if os.path.exists(os.path.join(srctree, '.git')): 890*4882a593Smuzhiyun # Check if it's a tree previously extracted by us. This is done 891*4882a593Smuzhiyun # by ensuring that devtool-base and args.branch (devtool) exist. 892*4882a593Smuzhiyun # The check_commits logic will cause an exception if either one 893*4882a593Smuzhiyun # of these doesn't exist 894*4882a593Smuzhiyun try: 895*4882a593Smuzhiyun (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree) 896*4882a593Smuzhiyun bb.process.run('git rev-parse %s' % args.branch, cwd=srctree) 897*4882a593Smuzhiyun except bb.process.ExecutionError: 898*4882a593Smuzhiyun stdout = '' 899*4882a593Smuzhiyun if stdout: 900*4882a593Smuzhiyun check_commits = True 901*4882a593Smuzhiyun for line in stdout.splitlines(): 902*4882a593Smuzhiyun if line.startswith('*'): 903*4882a593Smuzhiyun (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree) 904*4882a593Smuzhiyun initial_rev = stdout.rstrip() 905*4882a593Smuzhiyun if not initial_rev: 906*4882a593Smuzhiyun # Otherwise, just grab the head revision 907*4882a593Smuzhiyun (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree) 908*4882a593Smuzhiyun initial_rev = stdout.rstrip() 909*4882a593Smuzhiyun 910*4882a593Smuzhiyun branch_patches = {} 911*4882a593Smuzhiyun if check_commits: 912*4882a593Smuzhiyun # Check if there are override branches 913*4882a593Smuzhiyun (stdout, _) = bb.process.run('git branch', cwd=srctree) 914*4882a593Smuzhiyun branches = [] 915*4882a593Smuzhiyun for line in stdout.rstrip().splitlines(): 916*4882a593Smuzhiyun branchname = line[2:].rstrip() 917*4882a593Smuzhiyun if branchname.startswith(override_branch_prefix): 918*4882a593Smuzhiyun branches.append(branchname) 919*4882a593Smuzhiyun if branches: 920*4882a593Smuzhiyun logger.warning('SRC_URI is conditionally overridden in this recipe, thus several %s* branches have been created, one for each override that makes changes to SRC_URI. It is recommended that you make changes to the %s branch first, then checkout and rebase each %s* branch and update any unique patches there (duplicates on those branches will be ignored by devtool finish/update-recipe)' % (override_branch_prefix, args.branch, override_branch_prefix)) 921*4882a593Smuzhiyun branches.insert(0, args.branch) 922*4882a593Smuzhiyun seen_patches = [] 923*4882a593Smuzhiyun for branch in branches: 924*4882a593Smuzhiyun branch_patches[branch] = [] 925*4882a593Smuzhiyun (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree) 926*4882a593Smuzhiyun for line in stdout.splitlines(): 927*4882a593Smuzhiyun line = line.strip() 928*4882a593Smuzhiyun if line.startswith(oe.patch.GitApplyTree.patch_line_prefix): 929*4882a593Smuzhiyun origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip() 930*4882a593Smuzhiyun if not origpatch in seen_patches: 931*4882a593Smuzhiyun seen_patches.append(origpatch) 932*4882a593Smuzhiyun branch_patches[branch].append(origpatch) 933*4882a593Smuzhiyun 934*4882a593Smuzhiyun # Need to grab this here in case the source is within a subdirectory 935*4882a593Smuzhiyun srctreebase = srctree 936*4882a593Smuzhiyun srctree = get_real_srctree(srctree, rd.getVar('S'), rd.getVar('WORKDIR')) 937*4882a593Smuzhiyun 938*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(appendfile)) 939*4882a593Smuzhiyun with open(appendfile, 'w') as f: 940*4882a593Smuzhiyun f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n') 941*4882a593Smuzhiyun # Local files can be modified/tracked in separate subdir under srctree 942*4882a593Smuzhiyun # Mostly useful for packages with S != WORKDIR 943*4882a593Smuzhiyun f.write('FILESPATH:prepend := "%s:"\n' % 944*4882a593Smuzhiyun os.path.join(srctreebase, 'oe-local-files')) 945*4882a593Smuzhiyun f.write('# srctreebase: %s\n' % srctreebase) 946*4882a593Smuzhiyun 947*4882a593Smuzhiyun f.write('\ninherit externalsrc\n') 948*4882a593Smuzhiyun f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n') 949*4882a593Smuzhiyun f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree)) 950*4882a593Smuzhiyun 951*4882a593Smuzhiyun b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd) 952*4882a593Smuzhiyun if b_is_s: 953*4882a593Smuzhiyun f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree)) 954*4882a593Smuzhiyun 955*4882a593Smuzhiyun if bb.data.inherits_class('kernel', rd): 956*4882a593Smuzhiyun f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout ' 957*4882a593Smuzhiyun 'do_fetch do_unpack do_kernel_configcheck"\n') 958*4882a593Smuzhiyun f.write('\ndo_patch[noexec] = "1"\n') 959*4882a593Smuzhiyun f.write('\ndo_configure:append() {\n' 960*4882a593Smuzhiyun ' cp ${B}/.config ${S}/.config.baseline\n' 961*4882a593Smuzhiyun ' ln -sfT ${B}/.config ${S}/.config.new\n' 962*4882a593Smuzhiyun '}\n') 963*4882a593Smuzhiyun f.write('\ndo_kernel_configme:prepend() {\n' 964*4882a593Smuzhiyun ' if [ -e ${S}/.config ]; then\n' 965*4882a593Smuzhiyun ' mv ${S}/.config ${S}/.config.old\n' 966*4882a593Smuzhiyun ' fi\n' 967*4882a593Smuzhiyun '}\n') 968*4882a593Smuzhiyun if rd.getVarFlag('do_menuconfig','task'): 969*4882a593Smuzhiyun f.write('\ndo_configure:append() {\n' 970*4882a593Smuzhiyun ' if [ ! ${DEVTOOL_DISABLE_MENUCONFIG} ]; then\n' 971*4882a593Smuzhiyun ' cp ${B}/.config ${S}/.config.baseline\n' 972*4882a593Smuzhiyun ' ln -sfT ${B}/.config ${S}/.config.new\n' 973*4882a593Smuzhiyun ' fi\n' 974*4882a593Smuzhiyun '}\n') 975*4882a593Smuzhiyun if initial_rev: 976*4882a593Smuzhiyun f.write('\n# initial_rev: %s\n' % initial_rev) 977*4882a593Smuzhiyun for commit in commits: 978*4882a593Smuzhiyun f.write('# commit: %s\n' % commit) 979*4882a593Smuzhiyun if branch_patches: 980*4882a593Smuzhiyun for branch in branch_patches: 981*4882a593Smuzhiyun if branch == args.branch: 982*4882a593Smuzhiyun continue 983*4882a593Smuzhiyun f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch]))) 984*4882a593Smuzhiyun 985*4882a593Smuzhiyun update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn]) 986*4882a593Smuzhiyun 987*4882a593Smuzhiyun _add_md5(config, pn, appendfile) 988*4882a593Smuzhiyun 989*4882a593Smuzhiyun logger.info('Recipe %s now set up to build from %s' % (pn, srctree)) 990*4882a593Smuzhiyun 991*4882a593Smuzhiyun finally: 992*4882a593Smuzhiyun tinfoil.shutdown() 993*4882a593Smuzhiyun 994*4882a593Smuzhiyun return 0 995*4882a593Smuzhiyun 996*4882a593Smuzhiyun 997*4882a593Smuzhiyundef rename(args, config, basepath, workspace): 998*4882a593Smuzhiyun """Entry point for the devtool 'rename' subcommand""" 999*4882a593Smuzhiyun import bb 1000*4882a593Smuzhiyun import oe.recipeutils 1001*4882a593Smuzhiyun 1002*4882a593Smuzhiyun check_workspace_recipe(workspace, args.recipename) 1003*4882a593Smuzhiyun 1004*4882a593Smuzhiyun if not (args.newname or args.version): 1005*4882a593Smuzhiyun raise DevtoolError('You must specify a new name, a version with -V/--version, or both') 1006*4882a593Smuzhiyun 1007*4882a593Smuzhiyun recipefile = workspace[args.recipename]['recipefile'] 1008*4882a593Smuzhiyun if not recipefile: 1009*4882a593Smuzhiyun raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)') 1010*4882a593Smuzhiyun 1011*4882a593Smuzhiyun if args.newname and args.newname != args.recipename: 1012*4882a593Smuzhiyun reason = oe.recipeutils.validate_pn(args.newname) 1013*4882a593Smuzhiyun if reason: 1014*4882a593Smuzhiyun raise DevtoolError(reason) 1015*4882a593Smuzhiyun newname = args.newname 1016*4882a593Smuzhiyun else: 1017*4882a593Smuzhiyun newname = args.recipename 1018*4882a593Smuzhiyun 1019*4882a593Smuzhiyun append = workspace[args.recipename]['bbappend'] 1020*4882a593Smuzhiyun appendfn = os.path.splitext(os.path.basename(append))[0] 1021*4882a593Smuzhiyun splitfn = appendfn.split('_') 1022*4882a593Smuzhiyun if len(splitfn) > 1: 1023*4882a593Smuzhiyun origfnver = appendfn.split('_')[1] 1024*4882a593Smuzhiyun else: 1025*4882a593Smuzhiyun origfnver = '' 1026*4882a593Smuzhiyun 1027*4882a593Smuzhiyun recipefilemd5 = None 1028*4882a593Smuzhiyun tinfoil = setup_tinfoil(basepath=basepath, tracking=True) 1029*4882a593Smuzhiyun try: 1030*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, args.recipename, True) 1031*4882a593Smuzhiyun if not rd: 1032*4882a593Smuzhiyun return 1 1033*4882a593Smuzhiyun 1034*4882a593Smuzhiyun bp = rd.getVar('BP') 1035*4882a593Smuzhiyun bpn = rd.getVar('BPN') 1036*4882a593Smuzhiyun if newname != args.recipename: 1037*4882a593Smuzhiyun localdata = rd.createCopy() 1038*4882a593Smuzhiyun localdata.setVar('PN', newname) 1039*4882a593Smuzhiyun newbpn = localdata.getVar('BPN') 1040*4882a593Smuzhiyun else: 1041*4882a593Smuzhiyun newbpn = bpn 1042*4882a593Smuzhiyun s = rd.getVar('S', False) 1043*4882a593Smuzhiyun src_uri = rd.getVar('SRC_URI', False) 1044*4882a593Smuzhiyun pv = rd.getVar('PV') 1045*4882a593Smuzhiyun 1046*4882a593Smuzhiyun # Correct variable values that refer to the upstream source - these 1047*4882a593Smuzhiyun # values must stay the same, so if the name/version are changing then 1048*4882a593Smuzhiyun # we need to fix them up 1049*4882a593Smuzhiyun new_s = s 1050*4882a593Smuzhiyun new_src_uri = src_uri 1051*4882a593Smuzhiyun if newbpn != bpn: 1052*4882a593Smuzhiyun # ${PN} here is technically almost always incorrect, but people do use it 1053*4882a593Smuzhiyun new_s = new_s.replace('${BPN}', bpn) 1054*4882a593Smuzhiyun new_s = new_s.replace('${PN}', bpn) 1055*4882a593Smuzhiyun new_s = new_s.replace('${BP}', '%s-${PV}' % bpn) 1056*4882a593Smuzhiyun new_src_uri = new_src_uri.replace('${BPN}', bpn) 1057*4882a593Smuzhiyun new_src_uri = new_src_uri.replace('${PN}', bpn) 1058*4882a593Smuzhiyun new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn) 1059*4882a593Smuzhiyun if args.version and origfnver == pv: 1060*4882a593Smuzhiyun new_s = new_s.replace('${PV}', pv) 1061*4882a593Smuzhiyun new_s = new_s.replace('${BP}', '${BPN}-%s' % pv) 1062*4882a593Smuzhiyun new_src_uri = new_src_uri.replace('${PV}', pv) 1063*4882a593Smuzhiyun new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv) 1064*4882a593Smuzhiyun patchfields = {} 1065*4882a593Smuzhiyun if new_s != s: 1066*4882a593Smuzhiyun patchfields['S'] = new_s 1067*4882a593Smuzhiyun if new_src_uri != src_uri: 1068*4882a593Smuzhiyun patchfields['SRC_URI'] = new_src_uri 1069*4882a593Smuzhiyun if patchfields: 1070*4882a593Smuzhiyun recipefilemd5 = bb.utils.md5_file(recipefile) 1071*4882a593Smuzhiyun oe.recipeutils.patch_recipe(rd, recipefile, patchfields) 1072*4882a593Smuzhiyun newrecipefilemd5 = bb.utils.md5_file(recipefile) 1073*4882a593Smuzhiyun finally: 1074*4882a593Smuzhiyun tinfoil.shutdown() 1075*4882a593Smuzhiyun 1076*4882a593Smuzhiyun if args.version: 1077*4882a593Smuzhiyun newver = args.version 1078*4882a593Smuzhiyun else: 1079*4882a593Smuzhiyun newver = origfnver 1080*4882a593Smuzhiyun 1081*4882a593Smuzhiyun if newver: 1082*4882a593Smuzhiyun newappend = '%s_%s.bbappend' % (newname, newver) 1083*4882a593Smuzhiyun newfile = '%s_%s.bb' % (newname, newver) 1084*4882a593Smuzhiyun else: 1085*4882a593Smuzhiyun newappend = '%s.bbappend' % newname 1086*4882a593Smuzhiyun newfile = '%s.bb' % newname 1087*4882a593Smuzhiyun 1088*4882a593Smuzhiyun oldrecipedir = os.path.dirname(recipefile) 1089*4882a593Smuzhiyun newrecipedir = os.path.join(config.workspace_path, 'recipes', newname) 1090*4882a593Smuzhiyun if oldrecipedir != newrecipedir: 1091*4882a593Smuzhiyun bb.utils.mkdirhier(newrecipedir) 1092*4882a593Smuzhiyun 1093*4882a593Smuzhiyun newappend = os.path.join(os.path.dirname(append), newappend) 1094*4882a593Smuzhiyun newfile = os.path.join(newrecipedir, newfile) 1095*4882a593Smuzhiyun 1096*4882a593Smuzhiyun # Rename bbappend 1097*4882a593Smuzhiyun logger.info('Renaming %s to %s' % (append, newappend)) 1098*4882a593Smuzhiyun bb.utils.rename(append, newappend) 1099*4882a593Smuzhiyun # Rename recipe file 1100*4882a593Smuzhiyun logger.info('Renaming %s to %s' % (recipefile, newfile)) 1101*4882a593Smuzhiyun bb.utils.rename(recipefile, newfile) 1102*4882a593Smuzhiyun 1103*4882a593Smuzhiyun # Rename source tree if it's the default path 1104*4882a593Smuzhiyun appendmd5 = None 1105*4882a593Smuzhiyun if not args.no_srctree: 1106*4882a593Smuzhiyun srctree = workspace[args.recipename]['srctree'] 1107*4882a593Smuzhiyun if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename): 1108*4882a593Smuzhiyun newsrctree = os.path.join(config.workspace_path, 'sources', newname) 1109*4882a593Smuzhiyun logger.info('Renaming %s to %s' % (srctree, newsrctree)) 1110*4882a593Smuzhiyun shutil.move(srctree, newsrctree) 1111*4882a593Smuzhiyun # Correct any references (basically EXTERNALSRC*) in the .bbappend 1112*4882a593Smuzhiyun appendmd5 = bb.utils.md5_file(newappend) 1113*4882a593Smuzhiyun appendlines = [] 1114*4882a593Smuzhiyun with open(newappend, 'r') as f: 1115*4882a593Smuzhiyun for line in f: 1116*4882a593Smuzhiyun appendlines.append(line) 1117*4882a593Smuzhiyun with open(newappend, 'w') as f: 1118*4882a593Smuzhiyun for line in appendlines: 1119*4882a593Smuzhiyun if srctree in line: 1120*4882a593Smuzhiyun line = line.replace(srctree, newsrctree) 1121*4882a593Smuzhiyun f.write(line) 1122*4882a593Smuzhiyun newappendmd5 = bb.utils.md5_file(newappend) 1123*4882a593Smuzhiyun 1124*4882a593Smuzhiyun bpndir = None 1125*4882a593Smuzhiyun newbpndir = None 1126*4882a593Smuzhiyun if newbpn != bpn: 1127*4882a593Smuzhiyun bpndir = os.path.join(oldrecipedir, bpn) 1128*4882a593Smuzhiyun if os.path.exists(bpndir): 1129*4882a593Smuzhiyun newbpndir = os.path.join(newrecipedir, newbpn) 1130*4882a593Smuzhiyun logger.info('Renaming %s to %s' % (bpndir, newbpndir)) 1131*4882a593Smuzhiyun shutil.move(bpndir, newbpndir) 1132*4882a593Smuzhiyun 1133*4882a593Smuzhiyun bpdir = None 1134*4882a593Smuzhiyun newbpdir = None 1135*4882a593Smuzhiyun if newver != origfnver or newbpn != bpn: 1136*4882a593Smuzhiyun bpdir = os.path.join(oldrecipedir, bp) 1137*4882a593Smuzhiyun if os.path.exists(bpdir): 1138*4882a593Smuzhiyun newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver)) 1139*4882a593Smuzhiyun logger.info('Renaming %s to %s' % (bpdir, newbpdir)) 1140*4882a593Smuzhiyun shutil.move(bpdir, newbpdir) 1141*4882a593Smuzhiyun 1142*4882a593Smuzhiyun if oldrecipedir != newrecipedir: 1143*4882a593Smuzhiyun # Move any stray files and delete the old recipe directory 1144*4882a593Smuzhiyun for entry in os.listdir(oldrecipedir): 1145*4882a593Smuzhiyun oldpath = os.path.join(oldrecipedir, entry) 1146*4882a593Smuzhiyun newpath = os.path.join(newrecipedir, entry) 1147*4882a593Smuzhiyun logger.info('Renaming %s to %s' % (oldpath, newpath)) 1148*4882a593Smuzhiyun shutil.move(oldpath, newpath) 1149*4882a593Smuzhiyun os.rmdir(oldrecipedir) 1150*4882a593Smuzhiyun 1151*4882a593Smuzhiyun # Now take care of entries in .devtool_md5 1152*4882a593Smuzhiyun md5entries = [] 1153*4882a593Smuzhiyun with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f: 1154*4882a593Smuzhiyun for line in f: 1155*4882a593Smuzhiyun md5entries.append(line) 1156*4882a593Smuzhiyun 1157*4882a593Smuzhiyun if bpndir and newbpndir: 1158*4882a593Smuzhiyun relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/' 1159*4882a593Smuzhiyun else: 1160*4882a593Smuzhiyun relbpndir = None 1161*4882a593Smuzhiyun if bpdir and newbpdir: 1162*4882a593Smuzhiyun relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/' 1163*4882a593Smuzhiyun else: 1164*4882a593Smuzhiyun relbpdir = None 1165*4882a593Smuzhiyun 1166*4882a593Smuzhiyun with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f: 1167*4882a593Smuzhiyun for entry in md5entries: 1168*4882a593Smuzhiyun splitentry = entry.rstrip().split('|') 1169*4882a593Smuzhiyun if len(splitentry) > 2: 1170*4882a593Smuzhiyun if splitentry[0] == args.recipename: 1171*4882a593Smuzhiyun splitentry[0] = newname 1172*4882a593Smuzhiyun if splitentry[1] == os.path.relpath(append, config.workspace_path): 1173*4882a593Smuzhiyun splitentry[1] = os.path.relpath(newappend, config.workspace_path) 1174*4882a593Smuzhiyun if appendmd5 and splitentry[2] == appendmd5: 1175*4882a593Smuzhiyun splitentry[2] = newappendmd5 1176*4882a593Smuzhiyun elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path): 1177*4882a593Smuzhiyun splitentry[1] = os.path.relpath(newfile, config.workspace_path) 1178*4882a593Smuzhiyun if recipefilemd5 and splitentry[2] == recipefilemd5: 1179*4882a593Smuzhiyun splitentry[2] = newrecipefilemd5 1180*4882a593Smuzhiyun elif relbpndir and splitentry[1].startswith(relbpndir): 1181*4882a593Smuzhiyun splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path) 1182*4882a593Smuzhiyun elif relbpdir and splitentry[1].startswith(relbpdir): 1183*4882a593Smuzhiyun splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path) 1184*4882a593Smuzhiyun entry = '|'.join(splitentry) + '\n' 1185*4882a593Smuzhiyun f.write(entry) 1186*4882a593Smuzhiyun return 0 1187*4882a593Smuzhiyun 1188*4882a593Smuzhiyun 1189*4882a593Smuzhiyundef _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False): 1190*4882a593Smuzhiyun """Get initial and update rev of a recipe. These are the start point of the 1191*4882a593Smuzhiyun whole patchset and start point for the patches to be re-generated/updated. 1192*4882a593Smuzhiyun """ 1193*4882a593Smuzhiyun import bb 1194*4882a593Smuzhiyun 1195*4882a593Smuzhiyun # Get current branch 1196*4882a593Smuzhiyun stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD', 1197*4882a593Smuzhiyun cwd=srctree) 1198*4882a593Smuzhiyun branchname = stdout.rstrip() 1199*4882a593Smuzhiyun 1200*4882a593Smuzhiyun # Parse initial rev from recipe if not specified 1201*4882a593Smuzhiyun commits = [] 1202*4882a593Smuzhiyun patches = [] 1203*4882a593Smuzhiyun with open(recipe_path, 'r') as f: 1204*4882a593Smuzhiyun for line in f: 1205*4882a593Smuzhiyun if line.startswith('# initial_rev:'): 1206*4882a593Smuzhiyun if not initial_rev: 1207*4882a593Smuzhiyun initial_rev = line.split(':')[-1].strip() 1208*4882a593Smuzhiyun elif line.startswith('# commit:') and not force_patch_refresh: 1209*4882a593Smuzhiyun commits.append(line.split(':')[-1].strip()) 1210*4882a593Smuzhiyun elif line.startswith('# patches_%s:' % branchname): 1211*4882a593Smuzhiyun patches = line.split(':')[-1].strip().split(',') 1212*4882a593Smuzhiyun 1213*4882a593Smuzhiyun update_rev = initial_rev 1214*4882a593Smuzhiyun changed_revs = None 1215*4882a593Smuzhiyun if initial_rev: 1216*4882a593Smuzhiyun # Find first actually changed revision 1217*4882a593Smuzhiyun stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' % 1218*4882a593Smuzhiyun initial_rev, cwd=srctree) 1219*4882a593Smuzhiyun newcommits = stdout.split() 1220*4882a593Smuzhiyun for i in range(min(len(commits), len(newcommits))): 1221*4882a593Smuzhiyun if newcommits[i] == commits[i]: 1222*4882a593Smuzhiyun update_rev = commits[i] 1223*4882a593Smuzhiyun 1224*4882a593Smuzhiyun try: 1225*4882a593Smuzhiyun stdout, _ = bb.process.run('git cherry devtool-patched', 1226*4882a593Smuzhiyun cwd=srctree) 1227*4882a593Smuzhiyun except bb.process.ExecutionError as err: 1228*4882a593Smuzhiyun stdout = None 1229*4882a593Smuzhiyun 1230*4882a593Smuzhiyun if stdout is not None and not force_patch_refresh: 1231*4882a593Smuzhiyun changed_revs = [] 1232*4882a593Smuzhiyun for line in stdout.splitlines(): 1233*4882a593Smuzhiyun if line.startswith('+ '): 1234*4882a593Smuzhiyun rev = line.split()[1] 1235*4882a593Smuzhiyun if rev in newcommits: 1236*4882a593Smuzhiyun changed_revs.append(rev) 1237*4882a593Smuzhiyun 1238*4882a593Smuzhiyun return initial_rev, update_rev, changed_revs, patches 1239*4882a593Smuzhiyun 1240*4882a593Smuzhiyundef _remove_file_entries(srcuri, filelist): 1241*4882a593Smuzhiyun """Remove file:// entries from SRC_URI""" 1242*4882a593Smuzhiyun remaining = filelist[:] 1243*4882a593Smuzhiyun entries = [] 1244*4882a593Smuzhiyun for fname in filelist: 1245*4882a593Smuzhiyun basename = os.path.basename(fname) 1246*4882a593Smuzhiyun for i in range(len(srcuri)): 1247*4882a593Smuzhiyun if (srcuri[i].startswith('file://') and 1248*4882a593Smuzhiyun os.path.basename(srcuri[i].split(';')[0]) == basename): 1249*4882a593Smuzhiyun entries.append(srcuri[i]) 1250*4882a593Smuzhiyun remaining.remove(fname) 1251*4882a593Smuzhiyun srcuri.pop(i) 1252*4882a593Smuzhiyun break 1253*4882a593Smuzhiyun return entries, remaining 1254*4882a593Smuzhiyun 1255*4882a593Smuzhiyundef _replace_srcuri_entry(srcuri, filename, newentry): 1256*4882a593Smuzhiyun """Replace entry corresponding to specified file with a new entry""" 1257*4882a593Smuzhiyun basename = os.path.basename(filename) 1258*4882a593Smuzhiyun for i in range(len(srcuri)): 1259*4882a593Smuzhiyun if os.path.basename(srcuri[i].split(';')[0]) == basename: 1260*4882a593Smuzhiyun srcuri.pop(i) 1261*4882a593Smuzhiyun srcuri.insert(i, newentry) 1262*4882a593Smuzhiyun break 1263*4882a593Smuzhiyun 1264*4882a593Smuzhiyundef _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False): 1265*4882a593Smuzhiyun """Unlink existing patch files""" 1266*4882a593Smuzhiyun 1267*4882a593Smuzhiyun dry_run_suffix = ' (dry-run)' if dry_run else '' 1268*4882a593Smuzhiyun 1269*4882a593Smuzhiyun for path in files: 1270*4882a593Smuzhiyun if append: 1271*4882a593Smuzhiyun if not destpath: 1272*4882a593Smuzhiyun raise Exception('destpath should be set here') 1273*4882a593Smuzhiyun path = os.path.join(destpath, os.path.basename(path)) 1274*4882a593Smuzhiyun 1275*4882a593Smuzhiyun if os.path.exists(path): 1276*4882a593Smuzhiyun if not no_report_remove: 1277*4882a593Smuzhiyun logger.info('Removing file %s%s' % (path, dry_run_suffix)) 1278*4882a593Smuzhiyun if not dry_run: 1279*4882a593Smuzhiyun # FIXME "git rm" here would be nice if the file in question is 1280*4882a593Smuzhiyun # tracked 1281*4882a593Smuzhiyun # FIXME there's a chance that this file is referred to by 1282*4882a593Smuzhiyun # another recipe, in which case deleting wouldn't be the 1283*4882a593Smuzhiyun # right thing to do 1284*4882a593Smuzhiyun os.remove(path) 1285*4882a593Smuzhiyun # Remove directory if empty 1286*4882a593Smuzhiyun try: 1287*4882a593Smuzhiyun os.rmdir(os.path.dirname(path)) 1288*4882a593Smuzhiyun except OSError as ose: 1289*4882a593Smuzhiyun if ose.errno != errno.ENOTEMPTY: 1290*4882a593Smuzhiyun raise 1291*4882a593Smuzhiyun 1292*4882a593Smuzhiyun 1293*4882a593Smuzhiyundef _export_patches(srctree, rd, start_rev, destdir, changed_revs=None): 1294*4882a593Smuzhiyun """Export patches from srctree to given location. 1295*4882a593Smuzhiyun Returns three-tuple of dicts: 1296*4882a593Smuzhiyun 1. updated - patches that already exist in SRCURI 1297*4882a593Smuzhiyun 2. added - new patches that don't exist in SRCURI 1298*4882a593Smuzhiyun 3 removed - patches that exist in SRCURI but not in exported patches 1299*4882a593Smuzhiyun In each dict the key is the 'basepath' of the URI and value is the 1300*4882a593Smuzhiyun absolute path to the existing file in recipe space (if any). 1301*4882a593Smuzhiyun """ 1302*4882a593Smuzhiyun import oe.recipeutils 1303*4882a593Smuzhiyun from oe.patch import GitApplyTree 1304*4882a593Smuzhiyun updated = OrderedDict() 1305*4882a593Smuzhiyun added = OrderedDict() 1306*4882a593Smuzhiyun seqpatch_re = re.compile('^([0-9]{4}-)?(.+)') 1307*4882a593Smuzhiyun 1308*4882a593Smuzhiyun existing_patches = dict((os.path.basename(path), path) for path in 1309*4882a593Smuzhiyun oe.recipeutils.get_recipe_patches(rd)) 1310*4882a593Smuzhiyun logger.debug('Existing patches: %s' % existing_patches) 1311*4882a593Smuzhiyun 1312*4882a593Smuzhiyun # Generate patches from Git, exclude local files directory 1313*4882a593Smuzhiyun patch_pathspec = _git_exclude_path(srctree, 'oe-local-files') 1314*4882a593Smuzhiyun GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec) 1315*4882a593Smuzhiyun 1316*4882a593Smuzhiyun new_patches = sorted(os.listdir(destdir)) 1317*4882a593Smuzhiyun for new_patch in new_patches: 1318*4882a593Smuzhiyun # Strip numbering from patch names. If it's a git sequence named patch, 1319*4882a593Smuzhiyun # the numbers might not match up since we are starting from a different 1320*4882a593Smuzhiyun # revision This does assume that people are using unique shortlog 1321*4882a593Smuzhiyun # values, but they ought to be anyway... 1322*4882a593Smuzhiyun new_basename = seqpatch_re.match(new_patch).group(2) 1323*4882a593Smuzhiyun match_name = None 1324*4882a593Smuzhiyun for old_patch in existing_patches: 1325*4882a593Smuzhiyun old_basename = seqpatch_re.match(old_patch).group(2) 1326*4882a593Smuzhiyun old_basename_splitext = os.path.splitext(old_basename) 1327*4882a593Smuzhiyun if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename: 1328*4882a593Smuzhiyun old_patch_noext = os.path.splitext(old_patch)[0] 1329*4882a593Smuzhiyun match_name = old_patch_noext 1330*4882a593Smuzhiyun break 1331*4882a593Smuzhiyun elif new_basename == old_basename: 1332*4882a593Smuzhiyun match_name = old_patch 1333*4882a593Smuzhiyun break 1334*4882a593Smuzhiyun if match_name: 1335*4882a593Smuzhiyun # Rename patch files 1336*4882a593Smuzhiyun if new_patch != match_name: 1337*4882a593Smuzhiyun bb.utils.rename(os.path.join(destdir, new_patch), 1338*4882a593Smuzhiyun os.path.join(destdir, match_name)) 1339*4882a593Smuzhiyun # Need to pop it off the list now before checking changed_revs 1340*4882a593Smuzhiyun oldpath = existing_patches.pop(old_patch) 1341*4882a593Smuzhiyun if changed_revs is not None: 1342*4882a593Smuzhiyun # Avoid updating patches that have not actually changed 1343*4882a593Smuzhiyun with open(os.path.join(destdir, match_name), 'r') as f: 1344*4882a593Smuzhiyun firstlineitems = f.readline().split() 1345*4882a593Smuzhiyun # Looking for "From <hash>" line 1346*4882a593Smuzhiyun if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40: 1347*4882a593Smuzhiyun if not firstlineitems[1] in changed_revs: 1348*4882a593Smuzhiyun continue 1349*4882a593Smuzhiyun # Recompress if necessary 1350*4882a593Smuzhiyun if oldpath.endswith(('.gz', '.Z')): 1351*4882a593Smuzhiyun bb.process.run(['gzip', match_name], cwd=destdir) 1352*4882a593Smuzhiyun if oldpath.endswith('.gz'): 1353*4882a593Smuzhiyun match_name += '.gz' 1354*4882a593Smuzhiyun else: 1355*4882a593Smuzhiyun match_name += '.Z' 1356*4882a593Smuzhiyun elif oldpath.endswith('.bz2'): 1357*4882a593Smuzhiyun bb.process.run(['bzip2', match_name], cwd=destdir) 1358*4882a593Smuzhiyun match_name += '.bz2' 1359*4882a593Smuzhiyun updated[match_name] = oldpath 1360*4882a593Smuzhiyun else: 1361*4882a593Smuzhiyun added[new_patch] = None 1362*4882a593Smuzhiyun return (updated, added, existing_patches) 1363*4882a593Smuzhiyun 1364*4882a593Smuzhiyun 1365*4882a593Smuzhiyundef _create_kconfig_diff(srctree, rd, outfile): 1366*4882a593Smuzhiyun """Create a kconfig fragment""" 1367*4882a593Smuzhiyun # Only update config fragment if both config files exist 1368*4882a593Smuzhiyun orig_config = os.path.join(srctree, '.config.baseline') 1369*4882a593Smuzhiyun new_config = os.path.join(srctree, '.config.new') 1370*4882a593Smuzhiyun if os.path.exists(orig_config) and os.path.exists(new_config): 1371*4882a593Smuzhiyun cmd = ['diff', '--new-line-format=%L', '--old-line-format=', 1372*4882a593Smuzhiyun '--unchanged-line-format=', orig_config, new_config] 1373*4882a593Smuzhiyun pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, 1374*4882a593Smuzhiyun stderr=subprocess.PIPE) 1375*4882a593Smuzhiyun stdout, stderr = pipe.communicate() 1376*4882a593Smuzhiyun if pipe.returncode == 1: 1377*4882a593Smuzhiyun logger.info("Updating config fragment %s" % outfile) 1378*4882a593Smuzhiyun with open(outfile, 'wb') as fobj: 1379*4882a593Smuzhiyun fobj.write(stdout) 1380*4882a593Smuzhiyun elif pipe.returncode == 0: 1381*4882a593Smuzhiyun logger.info("Would remove config fragment %s" % outfile) 1382*4882a593Smuzhiyun if os.path.exists(outfile): 1383*4882a593Smuzhiyun # Remove fragment file in case of empty diff 1384*4882a593Smuzhiyun logger.info("Removing config fragment %s" % outfile) 1385*4882a593Smuzhiyun os.unlink(outfile) 1386*4882a593Smuzhiyun else: 1387*4882a593Smuzhiyun raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr) 1388*4882a593Smuzhiyun return True 1389*4882a593Smuzhiyun return False 1390*4882a593Smuzhiyun 1391*4882a593Smuzhiyun 1392*4882a593Smuzhiyundef _export_local_files(srctree, rd, destdir, srctreebase): 1393*4882a593Smuzhiyun """Copy local files from srctree to given location. 1394*4882a593Smuzhiyun Returns three-tuple of dicts: 1395*4882a593Smuzhiyun 1. updated - files that already exist in SRCURI 1396*4882a593Smuzhiyun 2. added - new files files that don't exist in SRCURI 1397*4882a593Smuzhiyun 3 removed - files that exist in SRCURI but not in exported files 1398*4882a593Smuzhiyun In each dict the key is the 'basepath' of the URI and value is the 1399*4882a593Smuzhiyun absolute path to the existing file in recipe space (if any). 1400*4882a593Smuzhiyun """ 1401*4882a593Smuzhiyun import oe.recipeutils 1402*4882a593Smuzhiyun 1403*4882a593Smuzhiyun # Find out local files (SRC_URI files that exist in the "recipe space"). 1404*4882a593Smuzhiyun # Local files that reside in srctree are not included in patch generation. 1405*4882a593Smuzhiyun # Instead they are directly copied over the original source files (in 1406*4882a593Smuzhiyun # recipe space). 1407*4882a593Smuzhiyun existing_files = oe.recipeutils.get_recipe_local_files(rd) 1408*4882a593Smuzhiyun new_set = None 1409*4882a593Smuzhiyun updated = OrderedDict() 1410*4882a593Smuzhiyun added = OrderedDict() 1411*4882a593Smuzhiyun removed = OrderedDict() 1412*4882a593Smuzhiyun 1413*4882a593Smuzhiyun # Get current branch and return early with empty lists 1414*4882a593Smuzhiyun # if on one of the override branches 1415*4882a593Smuzhiyun # (local files are provided only for the main branch and processing 1416*4882a593Smuzhiyun # them against lists from recipe overrides will result in mismatches 1417*4882a593Smuzhiyun # and broken modifications to recipes). 1418*4882a593Smuzhiyun stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD', 1419*4882a593Smuzhiyun cwd=srctree) 1420*4882a593Smuzhiyun branchname = stdout.rstrip() 1421*4882a593Smuzhiyun if branchname.startswith(override_branch_prefix): 1422*4882a593Smuzhiyun return (updated, added, removed) 1423*4882a593Smuzhiyun 1424*4882a593Smuzhiyun local_files_dir = os.path.join(srctreebase, 'oe-local-files') 1425*4882a593Smuzhiyun git_files = _git_ls_tree(srctree) 1426*4882a593Smuzhiyun if 'oe-local-files' in git_files: 1427*4882a593Smuzhiyun # If tracked by Git, take the files from srctree HEAD. First get 1428*4882a593Smuzhiyun # the tree object of the directory 1429*4882a593Smuzhiyun tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool') 1430*4882a593Smuzhiyun tree = git_files['oe-local-files'][2] 1431*4882a593Smuzhiyun bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree, 1432*4882a593Smuzhiyun env=dict(os.environ, GIT_WORK_TREE=destdir, 1433*4882a593Smuzhiyun GIT_INDEX_FILE=tmp_index)) 1434*4882a593Smuzhiyun new_set = list(_git_ls_tree(srctree, tree, True).keys()) 1435*4882a593Smuzhiyun elif os.path.isdir(local_files_dir): 1436*4882a593Smuzhiyun # If not tracked by Git, just copy from working copy 1437*4882a593Smuzhiyun new_set = _ls_tree(local_files_dir) 1438*4882a593Smuzhiyun bb.process.run(['cp', '-ax', 1439*4882a593Smuzhiyun os.path.join(local_files_dir, '.'), destdir]) 1440*4882a593Smuzhiyun else: 1441*4882a593Smuzhiyun new_set = [] 1442*4882a593Smuzhiyun 1443*4882a593Smuzhiyun # Special handling for kernel config 1444*4882a593Smuzhiyun if bb.data.inherits_class('kernel-yocto', rd): 1445*4882a593Smuzhiyun fragment_fn = 'devtool-fragment.cfg' 1446*4882a593Smuzhiyun fragment_path = os.path.join(destdir, fragment_fn) 1447*4882a593Smuzhiyun if _create_kconfig_diff(srctree, rd, fragment_path): 1448*4882a593Smuzhiyun if os.path.exists(fragment_path): 1449*4882a593Smuzhiyun if fragment_fn not in new_set: 1450*4882a593Smuzhiyun new_set.append(fragment_fn) 1451*4882a593Smuzhiyun # Copy fragment to local-files 1452*4882a593Smuzhiyun if os.path.isdir(local_files_dir): 1453*4882a593Smuzhiyun shutil.copy2(fragment_path, local_files_dir) 1454*4882a593Smuzhiyun else: 1455*4882a593Smuzhiyun if fragment_fn in new_set: 1456*4882a593Smuzhiyun new_set.remove(fragment_fn) 1457*4882a593Smuzhiyun # Remove fragment from local-files 1458*4882a593Smuzhiyun if os.path.exists(os.path.join(local_files_dir, fragment_fn)): 1459*4882a593Smuzhiyun os.unlink(os.path.join(local_files_dir, fragment_fn)) 1460*4882a593Smuzhiyun 1461*4882a593Smuzhiyun # Special handling for cml1, ccmake, etc bbclasses that generated 1462*4882a593Smuzhiyun # configuration fragment files that are consumed as source files 1463*4882a593Smuzhiyun for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]: 1464*4882a593Smuzhiyun if bb.data.inherits_class(frag_class, rd): 1465*4882a593Smuzhiyun srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name) 1466*4882a593Smuzhiyun if os.path.exists(srcpath): 1467*4882a593Smuzhiyun if frag_name not in new_set: 1468*4882a593Smuzhiyun new_set.append(frag_name) 1469*4882a593Smuzhiyun # copy fragment into destdir 1470*4882a593Smuzhiyun shutil.copy2(srcpath, destdir) 1471*4882a593Smuzhiyun # copy fragment into local files if exists 1472*4882a593Smuzhiyun if os.path.isdir(local_files_dir): 1473*4882a593Smuzhiyun shutil.copy2(srcpath, local_files_dir) 1474*4882a593Smuzhiyun 1475*4882a593Smuzhiyun if new_set is not None: 1476*4882a593Smuzhiyun for fname in new_set: 1477*4882a593Smuzhiyun if fname in existing_files: 1478*4882a593Smuzhiyun origpath = existing_files.pop(fname) 1479*4882a593Smuzhiyun workpath = os.path.join(local_files_dir, fname) 1480*4882a593Smuzhiyun if not filecmp.cmp(origpath, workpath): 1481*4882a593Smuzhiyun updated[fname] = origpath 1482*4882a593Smuzhiyun elif fname != '.gitignore': 1483*4882a593Smuzhiyun added[fname] = None 1484*4882a593Smuzhiyun 1485*4882a593Smuzhiyun workdir = rd.getVar('WORKDIR') 1486*4882a593Smuzhiyun s = rd.getVar('S') 1487*4882a593Smuzhiyun if not s.endswith(os.sep): 1488*4882a593Smuzhiyun s += os.sep 1489*4882a593Smuzhiyun 1490*4882a593Smuzhiyun if workdir != s: 1491*4882a593Smuzhiyun # Handle files where subdir= was specified 1492*4882a593Smuzhiyun for fname in list(existing_files.keys()): 1493*4882a593Smuzhiyun # FIXME handle both subdir starting with BP and not? 1494*4882a593Smuzhiyun fworkpath = os.path.join(workdir, fname) 1495*4882a593Smuzhiyun if fworkpath.startswith(s): 1496*4882a593Smuzhiyun fpath = os.path.join(srctree, os.path.relpath(fworkpath, s)) 1497*4882a593Smuzhiyun if os.path.exists(fpath): 1498*4882a593Smuzhiyun origpath = existing_files.pop(fname) 1499*4882a593Smuzhiyun if not filecmp.cmp(origpath, fpath): 1500*4882a593Smuzhiyun updated[fpath] = origpath 1501*4882a593Smuzhiyun 1502*4882a593Smuzhiyun removed = existing_files 1503*4882a593Smuzhiyun return (updated, added, removed) 1504*4882a593Smuzhiyun 1505*4882a593Smuzhiyun 1506*4882a593Smuzhiyundef _determine_files_dir(rd): 1507*4882a593Smuzhiyun """Determine the appropriate files directory for a recipe""" 1508*4882a593Smuzhiyun recipedir = rd.getVar('FILE_DIRNAME') 1509*4882a593Smuzhiyun for entry in rd.getVar('FILESPATH').split(':'): 1510*4882a593Smuzhiyun relpth = os.path.relpath(entry, recipedir) 1511*4882a593Smuzhiyun if not os.sep in relpth: 1512*4882a593Smuzhiyun # One (or zero) levels below only, so we don't put anything in machine-specific directories 1513*4882a593Smuzhiyun if os.path.isdir(entry): 1514*4882a593Smuzhiyun return entry 1515*4882a593Smuzhiyun return os.path.join(recipedir, rd.getVar('BPN')) 1516*4882a593Smuzhiyun 1517*4882a593Smuzhiyun 1518*4882a593Smuzhiyundef _update_recipe_srcrev(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir=None): 1519*4882a593Smuzhiyun """Implement the 'srcrev' mode of update-recipe""" 1520*4882a593Smuzhiyun import bb 1521*4882a593Smuzhiyun import oe.recipeutils 1522*4882a593Smuzhiyun 1523*4882a593Smuzhiyun dry_run_suffix = ' (dry-run)' if dry_run_outdir else '' 1524*4882a593Smuzhiyun 1525*4882a593Smuzhiyun recipefile = rd.getVar('FILE') 1526*4882a593Smuzhiyun recipedir = os.path.basename(recipefile) 1527*4882a593Smuzhiyun logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix)) 1528*4882a593Smuzhiyun 1529*4882a593Smuzhiyun # Get HEAD revision 1530*4882a593Smuzhiyun try: 1531*4882a593Smuzhiyun stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree) 1532*4882a593Smuzhiyun except bb.process.ExecutionError as err: 1533*4882a593Smuzhiyun raise DevtoolError('Failed to get HEAD revision in %s: %s' % 1534*4882a593Smuzhiyun (srctree, err)) 1535*4882a593Smuzhiyun srcrev = stdout.strip() 1536*4882a593Smuzhiyun if len(srcrev) != 40: 1537*4882a593Smuzhiyun raise DevtoolError('Invalid hash returned by git: %s' % stdout) 1538*4882a593Smuzhiyun 1539*4882a593Smuzhiyun destpath = None 1540*4882a593Smuzhiyun remove_files = [] 1541*4882a593Smuzhiyun patchfields = {} 1542*4882a593Smuzhiyun patchfields['SRCREV'] = srcrev 1543*4882a593Smuzhiyun orig_src_uri = rd.getVar('SRC_URI', False) or '' 1544*4882a593Smuzhiyun srcuri = orig_src_uri.split() 1545*4882a593Smuzhiyun tempdir = tempfile.mkdtemp(prefix='devtool') 1546*4882a593Smuzhiyun update_srcuri = False 1547*4882a593Smuzhiyun appendfile = None 1548*4882a593Smuzhiyun try: 1549*4882a593Smuzhiyun local_files_dir = tempfile.mkdtemp(dir=tempdir) 1550*4882a593Smuzhiyun srctreebase = workspace[recipename]['srctreebase'] 1551*4882a593Smuzhiyun upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase) 1552*4882a593Smuzhiyun if not no_remove: 1553*4882a593Smuzhiyun # Find list of existing patches in recipe file 1554*4882a593Smuzhiyun patches_dir = tempfile.mkdtemp(dir=tempdir) 1555*4882a593Smuzhiyun old_srcrev = rd.getVar('SRCREV') or '' 1556*4882a593Smuzhiyun upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev, 1557*4882a593Smuzhiyun patches_dir) 1558*4882a593Smuzhiyun logger.debug('Patches: update %s, new %s, delete %s' % (dict(upd_p), dict(new_p), dict(del_p))) 1559*4882a593Smuzhiyun 1560*4882a593Smuzhiyun # Remove deleted local files and "overlapping" patches 1561*4882a593Smuzhiyun remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values()) 1562*4882a593Smuzhiyun if remove_files: 1563*4882a593Smuzhiyun removedentries = _remove_file_entries(srcuri, remove_files)[0] 1564*4882a593Smuzhiyun update_srcuri = True 1565*4882a593Smuzhiyun 1566*4882a593Smuzhiyun if appendlayerdir: 1567*4882a593Smuzhiyun files = dict((os.path.join(local_files_dir, key), val) for 1568*4882a593Smuzhiyun key, val in list(upd_f.items()) + list(new_f.items())) 1569*4882a593Smuzhiyun removevalues = {} 1570*4882a593Smuzhiyun if update_srcuri: 1571*4882a593Smuzhiyun removevalues = {'SRC_URI': removedentries} 1572*4882a593Smuzhiyun patchfields['SRC_URI'] = '\\\n '.join(srcuri) 1573*4882a593Smuzhiyun if dry_run_outdir: 1574*4882a593Smuzhiyun logger.info('Creating bbappend (dry-run)') 1575*4882a593Smuzhiyun else: 1576*4882a593Smuzhiyun appendfile, destpath = oe.recipeutils.bbappend_recipe( 1577*4882a593Smuzhiyun rd, appendlayerdir, files, wildcardver=wildcard_version, 1578*4882a593Smuzhiyun extralines=patchfields, removevalues=removevalues, 1579*4882a593Smuzhiyun redirect_output=dry_run_outdir) 1580*4882a593Smuzhiyun else: 1581*4882a593Smuzhiyun files_dir = _determine_files_dir(rd) 1582*4882a593Smuzhiyun for basepath, path in upd_f.items(): 1583*4882a593Smuzhiyun logger.info('Updating file %s%s' % (basepath, dry_run_suffix)) 1584*4882a593Smuzhiyun if os.path.isabs(basepath): 1585*4882a593Smuzhiyun # Original file (probably with subdir pointing inside source tree) 1586*4882a593Smuzhiyun # so we do not want to move it, just copy 1587*4882a593Smuzhiyun _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir) 1588*4882a593Smuzhiyun else: 1589*4882a593Smuzhiyun _move_file(os.path.join(local_files_dir, basepath), path, 1590*4882a593Smuzhiyun dry_run_outdir=dry_run_outdir, base_outdir=recipedir) 1591*4882a593Smuzhiyun update_srcuri= True 1592*4882a593Smuzhiyun for basepath, path in new_f.items(): 1593*4882a593Smuzhiyun logger.info('Adding new file %s%s' % (basepath, dry_run_suffix)) 1594*4882a593Smuzhiyun _move_file(os.path.join(local_files_dir, basepath), 1595*4882a593Smuzhiyun os.path.join(files_dir, basepath), 1596*4882a593Smuzhiyun dry_run_outdir=dry_run_outdir, 1597*4882a593Smuzhiyun base_outdir=recipedir) 1598*4882a593Smuzhiyun srcuri.append('file://%s' % basepath) 1599*4882a593Smuzhiyun update_srcuri = True 1600*4882a593Smuzhiyun if update_srcuri: 1601*4882a593Smuzhiyun patchfields['SRC_URI'] = ' '.join(srcuri) 1602*4882a593Smuzhiyun ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir) 1603*4882a593Smuzhiyun finally: 1604*4882a593Smuzhiyun shutil.rmtree(tempdir) 1605*4882a593Smuzhiyun if not 'git://' in orig_src_uri: 1606*4882a593Smuzhiyun logger.info('You will need to update SRC_URI within the recipe to ' 1607*4882a593Smuzhiyun 'point to a git repository where you have pushed your ' 1608*4882a593Smuzhiyun 'changes') 1609*4882a593Smuzhiyun 1610*4882a593Smuzhiyun _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir) 1611*4882a593Smuzhiyun return True, appendfile, remove_files 1612*4882a593Smuzhiyun 1613*4882a593Smuzhiyundef _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, no_report_remove, initial_rev, dry_run_outdir=None, force_patch_refresh=False): 1614*4882a593Smuzhiyun """Implement the 'patch' mode of update-recipe""" 1615*4882a593Smuzhiyun import bb 1616*4882a593Smuzhiyun import oe.recipeutils 1617*4882a593Smuzhiyun 1618*4882a593Smuzhiyun recipefile = rd.getVar('FILE') 1619*4882a593Smuzhiyun recipedir = os.path.dirname(recipefile) 1620*4882a593Smuzhiyun append = workspace[recipename]['bbappend'] 1621*4882a593Smuzhiyun if not os.path.exists(append): 1622*4882a593Smuzhiyun raise DevtoolError('unable to find workspace bbappend for recipe %s' % 1623*4882a593Smuzhiyun recipename) 1624*4882a593Smuzhiyun srctreebase = workspace[recipename]['srctreebase'] 1625*4882a593Smuzhiyun relpatchdir = os.path.relpath(srctreebase, srctree) 1626*4882a593Smuzhiyun if relpatchdir == '.': 1627*4882a593Smuzhiyun patchdir_params = {} 1628*4882a593Smuzhiyun else: 1629*4882a593Smuzhiyun patchdir_params = {'patchdir': relpatchdir} 1630*4882a593Smuzhiyun 1631*4882a593Smuzhiyun def srcuri_entry(fname): 1632*4882a593Smuzhiyun if patchdir_params: 1633*4882a593Smuzhiyun paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items()) 1634*4882a593Smuzhiyun else: 1635*4882a593Smuzhiyun paramstr = '' 1636*4882a593Smuzhiyun return 'file://%s%s' % (basepath, paramstr) 1637*4882a593Smuzhiyun 1638*4882a593Smuzhiyun initial_rev, update_rev, changed_revs, filter_patches = _get_patchset_revs(srctree, append, initial_rev, force_patch_refresh) 1639*4882a593Smuzhiyun if not initial_rev: 1640*4882a593Smuzhiyun raise DevtoolError('Unable to find initial revision - please specify ' 1641*4882a593Smuzhiyun 'it with --initial-rev') 1642*4882a593Smuzhiyun 1643*4882a593Smuzhiyun appendfile = None 1644*4882a593Smuzhiyun dl_dir = rd.getVar('DL_DIR') 1645*4882a593Smuzhiyun if not dl_dir.endswith('/'): 1646*4882a593Smuzhiyun dl_dir += '/' 1647*4882a593Smuzhiyun 1648*4882a593Smuzhiyun dry_run_suffix = ' (dry-run)' if dry_run_outdir else '' 1649*4882a593Smuzhiyun 1650*4882a593Smuzhiyun tempdir = tempfile.mkdtemp(prefix='devtool') 1651*4882a593Smuzhiyun try: 1652*4882a593Smuzhiyun local_files_dir = tempfile.mkdtemp(dir=tempdir) 1653*4882a593Smuzhiyun upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase) 1654*4882a593Smuzhiyun 1655*4882a593Smuzhiyun # Get updated patches from source tree 1656*4882a593Smuzhiyun patches_dir = tempfile.mkdtemp(dir=tempdir) 1657*4882a593Smuzhiyun upd_p, new_p, _ = _export_patches(srctree, rd, update_rev, 1658*4882a593Smuzhiyun patches_dir, changed_revs) 1659*4882a593Smuzhiyun # Get all patches from source tree and check if any should be removed 1660*4882a593Smuzhiyun all_patches_dir = tempfile.mkdtemp(dir=tempdir) 1661*4882a593Smuzhiyun _, _, del_p = _export_patches(srctree, rd, initial_rev, 1662*4882a593Smuzhiyun all_patches_dir) 1663*4882a593Smuzhiyun logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p))) 1664*4882a593Smuzhiyun if filter_patches: 1665*4882a593Smuzhiyun new_p = OrderedDict() 1666*4882a593Smuzhiyun upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches) 1667*4882a593Smuzhiyun del_p = OrderedDict((k,v) for k,v in del_p.items() if k in filter_patches) 1668*4882a593Smuzhiyun remove_files = [] 1669*4882a593Smuzhiyun if not no_remove: 1670*4882a593Smuzhiyun # Remove deleted local files and patches 1671*4882a593Smuzhiyun remove_files = list(del_f.values()) + list(del_p.values()) 1672*4882a593Smuzhiyun updatefiles = False 1673*4882a593Smuzhiyun updaterecipe = False 1674*4882a593Smuzhiyun destpath = None 1675*4882a593Smuzhiyun srcuri = (rd.getVar('SRC_URI', False) or '').split() 1676*4882a593Smuzhiyun if appendlayerdir: 1677*4882a593Smuzhiyun files = OrderedDict((os.path.join(local_files_dir, key), val) for 1678*4882a593Smuzhiyun key, val in list(upd_f.items()) + list(new_f.items())) 1679*4882a593Smuzhiyun files.update(OrderedDict((os.path.join(patches_dir, key), val) for 1680*4882a593Smuzhiyun key, val in list(upd_p.items()) + list(new_p.items()))) 1681*4882a593Smuzhiyun if files or remove_files: 1682*4882a593Smuzhiyun removevalues = None 1683*4882a593Smuzhiyun if remove_files: 1684*4882a593Smuzhiyun removedentries, remaining = _remove_file_entries( 1685*4882a593Smuzhiyun srcuri, remove_files) 1686*4882a593Smuzhiyun if removedentries or remaining: 1687*4882a593Smuzhiyun remaining = [srcuri_entry(os.path.basename(item)) for 1688*4882a593Smuzhiyun item in remaining] 1689*4882a593Smuzhiyun removevalues = {'SRC_URI': removedentries + remaining} 1690*4882a593Smuzhiyun appendfile, destpath = oe.recipeutils.bbappend_recipe( 1691*4882a593Smuzhiyun rd, appendlayerdir, files, 1692*4882a593Smuzhiyun wildcardver=wildcard_version, 1693*4882a593Smuzhiyun removevalues=removevalues, 1694*4882a593Smuzhiyun redirect_output=dry_run_outdir, 1695*4882a593Smuzhiyun params=[patchdir_params] * len(files)) 1696*4882a593Smuzhiyun else: 1697*4882a593Smuzhiyun logger.info('No patches or local source files needed updating') 1698*4882a593Smuzhiyun else: 1699*4882a593Smuzhiyun # Update existing files 1700*4882a593Smuzhiyun files_dir = _determine_files_dir(rd) 1701*4882a593Smuzhiyun for basepath, path in upd_f.items(): 1702*4882a593Smuzhiyun logger.info('Updating file %s' % basepath) 1703*4882a593Smuzhiyun if os.path.isabs(basepath): 1704*4882a593Smuzhiyun # Original file (probably with subdir pointing inside source tree) 1705*4882a593Smuzhiyun # so we do not want to move it, just copy 1706*4882a593Smuzhiyun _copy_file(basepath, path, 1707*4882a593Smuzhiyun dry_run_outdir=dry_run_outdir, base_outdir=recipedir) 1708*4882a593Smuzhiyun else: 1709*4882a593Smuzhiyun _move_file(os.path.join(local_files_dir, basepath), path, 1710*4882a593Smuzhiyun dry_run_outdir=dry_run_outdir, base_outdir=recipedir) 1711*4882a593Smuzhiyun updatefiles = True 1712*4882a593Smuzhiyun for basepath, path in upd_p.items(): 1713*4882a593Smuzhiyun patchfn = os.path.join(patches_dir, basepath) 1714*4882a593Smuzhiyun if os.path.dirname(path) + '/' == dl_dir: 1715*4882a593Smuzhiyun # This is a a downloaded patch file - we now need to 1716*4882a593Smuzhiyun # replace the entry in SRC_URI with our local version 1717*4882a593Smuzhiyun logger.info('Replacing remote patch %s with updated local version' % basepath) 1718*4882a593Smuzhiyun path = os.path.join(files_dir, basepath) 1719*4882a593Smuzhiyun _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath)) 1720*4882a593Smuzhiyun updaterecipe = True 1721*4882a593Smuzhiyun else: 1722*4882a593Smuzhiyun logger.info('Updating patch %s%s' % (basepath, dry_run_suffix)) 1723*4882a593Smuzhiyun _move_file(patchfn, path, 1724*4882a593Smuzhiyun dry_run_outdir=dry_run_outdir, base_outdir=recipedir) 1725*4882a593Smuzhiyun updatefiles = True 1726*4882a593Smuzhiyun # Add any new files 1727*4882a593Smuzhiyun for basepath, path in new_f.items(): 1728*4882a593Smuzhiyun logger.info('Adding new file %s%s' % (basepath, dry_run_suffix)) 1729*4882a593Smuzhiyun _move_file(os.path.join(local_files_dir, basepath), 1730*4882a593Smuzhiyun os.path.join(files_dir, basepath), 1731*4882a593Smuzhiyun dry_run_outdir=dry_run_outdir, 1732*4882a593Smuzhiyun base_outdir=recipedir) 1733*4882a593Smuzhiyun srcuri.append(srcuri_entry(basepath)) 1734*4882a593Smuzhiyun updaterecipe = True 1735*4882a593Smuzhiyun for basepath, path in new_p.items(): 1736*4882a593Smuzhiyun logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix)) 1737*4882a593Smuzhiyun _move_file(os.path.join(patches_dir, basepath), 1738*4882a593Smuzhiyun os.path.join(files_dir, basepath), 1739*4882a593Smuzhiyun dry_run_outdir=dry_run_outdir, 1740*4882a593Smuzhiyun base_outdir=recipedir) 1741*4882a593Smuzhiyun srcuri.append(srcuri_entry(basepath)) 1742*4882a593Smuzhiyun updaterecipe = True 1743*4882a593Smuzhiyun # Update recipe, if needed 1744*4882a593Smuzhiyun if _remove_file_entries(srcuri, remove_files)[0]: 1745*4882a593Smuzhiyun updaterecipe = True 1746*4882a593Smuzhiyun if updaterecipe: 1747*4882a593Smuzhiyun if not dry_run_outdir: 1748*4882a593Smuzhiyun logger.info('Updating recipe %s' % os.path.basename(recipefile)) 1749*4882a593Smuzhiyun ret = oe.recipeutils.patch_recipe(rd, recipefile, 1750*4882a593Smuzhiyun {'SRC_URI': ' '.join(srcuri)}, 1751*4882a593Smuzhiyun redirect_output=dry_run_outdir) 1752*4882a593Smuzhiyun elif not updatefiles: 1753*4882a593Smuzhiyun # Neither patches nor recipe were updated 1754*4882a593Smuzhiyun logger.info('No patches or files need updating') 1755*4882a593Smuzhiyun return False, None, [] 1756*4882a593Smuzhiyun finally: 1757*4882a593Smuzhiyun shutil.rmtree(tempdir) 1758*4882a593Smuzhiyun 1759*4882a593Smuzhiyun _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir) 1760*4882a593Smuzhiyun return True, appendfile, remove_files 1761*4882a593Smuzhiyun 1762*4882a593Smuzhiyundef _guess_recipe_update_mode(srctree, rdata): 1763*4882a593Smuzhiyun """Guess the recipe update mode to use""" 1764*4882a593Smuzhiyun src_uri = (rdata.getVar('SRC_URI') or '').split() 1765*4882a593Smuzhiyun git_uris = [uri for uri in src_uri if uri.startswith('git://')] 1766*4882a593Smuzhiyun if not git_uris: 1767*4882a593Smuzhiyun return 'patch' 1768*4882a593Smuzhiyun # Just use the first URI for now 1769*4882a593Smuzhiyun uri = git_uris[0] 1770*4882a593Smuzhiyun # Check remote branch 1771*4882a593Smuzhiyun params = bb.fetch.decodeurl(uri)[5] 1772*4882a593Smuzhiyun upstr_branch = params['branch'] if 'branch' in params else 'master' 1773*4882a593Smuzhiyun # Check if current branch HEAD is found in upstream branch 1774*4882a593Smuzhiyun stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree) 1775*4882a593Smuzhiyun head_rev = stdout.rstrip() 1776*4882a593Smuzhiyun stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev, 1777*4882a593Smuzhiyun cwd=srctree) 1778*4882a593Smuzhiyun remote_brs = [branch.strip() for branch in stdout.splitlines()] 1779*4882a593Smuzhiyun if 'origin/' + upstr_branch in remote_brs: 1780*4882a593Smuzhiyun return 'srcrev' 1781*4882a593Smuzhiyun 1782*4882a593Smuzhiyun return 'patch' 1783*4882a593Smuzhiyun 1784*4882a593Smuzhiyundef _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): 1785*4882a593Smuzhiyun srctree = workspace[recipename]['srctree'] 1786*4882a593Smuzhiyun if mode == 'auto': 1787*4882a593Smuzhiyun mode = _guess_recipe_update_mode(srctree, rd) 1788*4882a593Smuzhiyun 1789*4882a593Smuzhiyun override_branches = [] 1790*4882a593Smuzhiyun mainbranch = None 1791*4882a593Smuzhiyun startbranch = None 1792*4882a593Smuzhiyun if not no_overrides: 1793*4882a593Smuzhiyun stdout, _ = bb.process.run('git branch', cwd=srctree) 1794*4882a593Smuzhiyun other_branches = [] 1795*4882a593Smuzhiyun for line in stdout.splitlines(): 1796*4882a593Smuzhiyun branchname = line[2:] 1797*4882a593Smuzhiyun if line.startswith('* '): 1798*4882a593Smuzhiyun startbranch = branchname 1799*4882a593Smuzhiyun if branchname.startswith(override_branch_prefix): 1800*4882a593Smuzhiyun override_branches.append(branchname) 1801*4882a593Smuzhiyun else: 1802*4882a593Smuzhiyun other_branches.append(branchname) 1803*4882a593Smuzhiyun 1804*4882a593Smuzhiyun if override_branches: 1805*4882a593Smuzhiyun logger.debug('_update_recipe: override branches: %s' % override_branches) 1806*4882a593Smuzhiyun logger.debug('_update_recipe: other branches: %s' % other_branches) 1807*4882a593Smuzhiyun if startbranch.startswith(override_branch_prefix): 1808*4882a593Smuzhiyun if len(other_branches) == 1: 1809*4882a593Smuzhiyun mainbranch = other_branches[1] 1810*4882a593Smuzhiyun else: 1811*4882a593Smuzhiyun raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first') 1812*4882a593Smuzhiyun else: 1813*4882a593Smuzhiyun mainbranch = startbranch 1814*4882a593Smuzhiyun 1815*4882a593Smuzhiyun checkedout = None 1816*4882a593Smuzhiyun anyupdated = False 1817*4882a593Smuzhiyun appendfile = None 1818*4882a593Smuzhiyun allremoved = [] 1819*4882a593Smuzhiyun if override_branches: 1820*4882a593Smuzhiyun logger.info('Handling main branch (%s)...' % mainbranch) 1821*4882a593Smuzhiyun if startbranch != mainbranch: 1822*4882a593Smuzhiyun bb.process.run('git checkout %s' % mainbranch, cwd=srctree) 1823*4882a593Smuzhiyun checkedout = mainbranch 1824*4882a593Smuzhiyun try: 1825*4882a593Smuzhiyun branchlist = [mainbranch] + override_branches 1826*4882a593Smuzhiyun for branch in branchlist: 1827*4882a593Smuzhiyun crd = bb.data.createCopy(rd) 1828*4882a593Smuzhiyun if branch != mainbranch: 1829*4882a593Smuzhiyun logger.info('Handling branch %s...' % branch) 1830*4882a593Smuzhiyun override = branch[len(override_branch_prefix):] 1831*4882a593Smuzhiyun crd.appendVar('OVERRIDES', ':%s' % override) 1832*4882a593Smuzhiyun bb.process.run('git checkout %s' % branch, cwd=srctree) 1833*4882a593Smuzhiyun checkedout = branch 1834*4882a593Smuzhiyun 1835*4882a593Smuzhiyun if mode == 'srcrev': 1836*4882a593Smuzhiyun updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir) 1837*4882a593Smuzhiyun elif mode == 'patch': 1838*4882a593Smuzhiyun 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) 1839*4882a593Smuzhiyun else: 1840*4882a593Smuzhiyun raise DevtoolError('update_recipe: invalid mode %s' % mode) 1841*4882a593Smuzhiyun if updated: 1842*4882a593Smuzhiyun anyupdated = True 1843*4882a593Smuzhiyun if appendf: 1844*4882a593Smuzhiyun appendfile = appendf 1845*4882a593Smuzhiyun allremoved.extend(removed) 1846*4882a593Smuzhiyun finally: 1847*4882a593Smuzhiyun if startbranch and checkedout != startbranch: 1848*4882a593Smuzhiyun bb.process.run('git checkout %s' % startbranch, cwd=srctree) 1849*4882a593Smuzhiyun 1850*4882a593Smuzhiyun return anyupdated, appendfile, allremoved 1851*4882a593Smuzhiyun 1852*4882a593Smuzhiyundef update_recipe(args, config, basepath, workspace): 1853*4882a593Smuzhiyun """Entry point for the devtool 'update-recipe' subcommand""" 1854*4882a593Smuzhiyun check_workspace_recipe(workspace, args.recipename) 1855*4882a593Smuzhiyun 1856*4882a593Smuzhiyun if args.append: 1857*4882a593Smuzhiyun if not os.path.exists(args.append): 1858*4882a593Smuzhiyun raise DevtoolError('bbappend destination layer directory "%s" ' 1859*4882a593Smuzhiyun 'does not exist' % args.append) 1860*4882a593Smuzhiyun if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')): 1861*4882a593Smuzhiyun raise DevtoolError('conf/layer.conf not found in bbappend ' 1862*4882a593Smuzhiyun 'destination layer "%s"' % args.append) 1863*4882a593Smuzhiyun 1864*4882a593Smuzhiyun tinfoil = setup_tinfoil(basepath=basepath, tracking=True) 1865*4882a593Smuzhiyun try: 1866*4882a593Smuzhiyun 1867*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, args.recipename, True) 1868*4882a593Smuzhiyun if not rd: 1869*4882a593Smuzhiyun return 1 1870*4882a593Smuzhiyun 1871*4882a593Smuzhiyun dry_run_output = None 1872*4882a593Smuzhiyun dry_run_outdir = None 1873*4882a593Smuzhiyun if args.dry_run: 1874*4882a593Smuzhiyun dry_run_output = tempfile.TemporaryDirectory(prefix='devtool') 1875*4882a593Smuzhiyun dry_run_outdir = dry_run_output.name 1876*4882a593Smuzhiyun updated, _, _ = _update_recipe(args.recipename, workspace, rd, args.mode, args.append, args.wildcard_version, args.no_remove, args.initial_rev, dry_run_outdir=dry_run_outdir, no_overrides=args.no_overrides, force_patch_refresh=args.force_patch_refresh) 1877*4882a593Smuzhiyun 1878*4882a593Smuzhiyun if updated: 1879*4882a593Smuzhiyun rf = rd.getVar('FILE') 1880*4882a593Smuzhiyun if rf.startswith(config.workspace_path): 1881*4882a593Smuzhiyun logger.warning('Recipe file %s has been updated but is inside the workspace - you will need to move it (and any associated files next to it) out to the desired layer before using "devtool reset" in order to keep any changes' % rf) 1882*4882a593Smuzhiyun finally: 1883*4882a593Smuzhiyun tinfoil.shutdown() 1884*4882a593Smuzhiyun 1885*4882a593Smuzhiyun return 0 1886*4882a593Smuzhiyun 1887*4882a593Smuzhiyun 1888*4882a593Smuzhiyundef status(args, config, basepath, workspace): 1889*4882a593Smuzhiyun """Entry point for the devtool 'status' subcommand""" 1890*4882a593Smuzhiyun if workspace: 1891*4882a593Smuzhiyun for recipe, value in sorted(workspace.items()): 1892*4882a593Smuzhiyun recipefile = value['recipefile'] 1893*4882a593Smuzhiyun if recipefile: 1894*4882a593Smuzhiyun recipestr = ' (%s)' % recipefile 1895*4882a593Smuzhiyun else: 1896*4882a593Smuzhiyun recipestr = '' 1897*4882a593Smuzhiyun print("%s: %s%s" % (recipe, value['srctree'], recipestr)) 1898*4882a593Smuzhiyun else: 1899*4882a593Smuzhiyun logger.info('No recipes currently in your workspace - you can use "devtool modify" to work on an existing recipe or "devtool add" to add a new one') 1900*4882a593Smuzhiyun return 0 1901*4882a593Smuzhiyun 1902*4882a593Smuzhiyun 1903*4882a593Smuzhiyundef _reset(recipes, no_clean, remove_work, config, basepath, workspace): 1904*4882a593Smuzhiyun """Reset one or more recipes""" 1905*4882a593Smuzhiyun import oe.path 1906*4882a593Smuzhiyun 1907*4882a593Smuzhiyun def clean_preferred_provider(pn, layerconf_path): 1908*4882a593Smuzhiyun """Remove PREFERRED_PROVIDER from layer.conf'""" 1909*4882a593Smuzhiyun import re 1910*4882a593Smuzhiyun layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf') 1911*4882a593Smuzhiyun new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf') 1912*4882a593Smuzhiyun pprovider_found = False 1913*4882a593Smuzhiyun with open(layerconf_file, 'r') as f: 1914*4882a593Smuzhiyun lines = f.readlines() 1915*4882a593Smuzhiyun with open(new_layerconf_file, 'a') as nf: 1916*4882a593Smuzhiyun for line in lines: 1917*4882a593Smuzhiyun pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$' 1918*4882a593Smuzhiyun if not re.match(pprovider_exp, line): 1919*4882a593Smuzhiyun nf.write(line) 1920*4882a593Smuzhiyun else: 1921*4882a593Smuzhiyun pprovider_found = True 1922*4882a593Smuzhiyun if pprovider_found: 1923*4882a593Smuzhiyun shutil.move(new_layerconf_file, layerconf_file) 1924*4882a593Smuzhiyun else: 1925*4882a593Smuzhiyun os.remove(new_layerconf_file) 1926*4882a593Smuzhiyun 1927*4882a593Smuzhiyun if recipes and not no_clean: 1928*4882a593Smuzhiyun if len(recipes) == 1: 1929*4882a593Smuzhiyun logger.info('Cleaning sysroot for recipe %s...' % recipes[0]) 1930*4882a593Smuzhiyun else: 1931*4882a593Smuzhiyun logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes)) 1932*4882a593Smuzhiyun # If the recipe file itself was created in the workspace, and 1933*4882a593Smuzhiyun # it uses BBCLASSEXTEND, then we need to also clean the other 1934*4882a593Smuzhiyun # variants 1935*4882a593Smuzhiyun targets = [] 1936*4882a593Smuzhiyun for recipe in recipes: 1937*4882a593Smuzhiyun targets.append(recipe) 1938*4882a593Smuzhiyun recipefile = workspace[recipe]['recipefile'] 1939*4882a593Smuzhiyun if recipefile and os.path.exists(recipefile): 1940*4882a593Smuzhiyun targets.extend(get_bbclassextend_targets(recipefile, recipe)) 1941*4882a593Smuzhiyun try: 1942*4882a593Smuzhiyun exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets)) 1943*4882a593Smuzhiyun except bb.process.ExecutionError as e: 1944*4882a593Smuzhiyun raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you ' 1945*4882a593Smuzhiyun 'wish, you may specify -n/--no-clean to ' 1946*4882a593Smuzhiyun 'skip running this command when resetting' % 1947*4882a593Smuzhiyun (e.command, e.stdout)) 1948*4882a593Smuzhiyun 1949*4882a593Smuzhiyun for pn in recipes: 1950*4882a593Smuzhiyun _check_preserve(config, pn) 1951*4882a593Smuzhiyun 1952*4882a593Smuzhiyun appendfile = workspace[pn]['bbappend'] 1953*4882a593Smuzhiyun if os.path.exists(appendfile): 1954*4882a593Smuzhiyun # This shouldn't happen, but is possible if devtool errored out prior to 1955*4882a593Smuzhiyun # writing the md5 file. We need to delete this here or the recipe won't 1956*4882a593Smuzhiyun # actually be reset 1957*4882a593Smuzhiyun os.remove(appendfile) 1958*4882a593Smuzhiyun 1959*4882a593Smuzhiyun preservepath = os.path.join(config.workspace_path, 'attic', pn, pn) 1960*4882a593Smuzhiyun def preservedir(origdir): 1961*4882a593Smuzhiyun if os.path.exists(origdir): 1962*4882a593Smuzhiyun for root, dirs, files in os.walk(origdir): 1963*4882a593Smuzhiyun for fn in files: 1964*4882a593Smuzhiyun logger.warning('Preserving %s in %s' % (fn, preservepath)) 1965*4882a593Smuzhiyun _move_file(os.path.join(origdir, fn), 1966*4882a593Smuzhiyun os.path.join(preservepath, fn)) 1967*4882a593Smuzhiyun for dn in dirs: 1968*4882a593Smuzhiyun preservedir(os.path.join(root, dn)) 1969*4882a593Smuzhiyun os.rmdir(origdir) 1970*4882a593Smuzhiyun 1971*4882a593Smuzhiyun recipefile = workspace[pn]['recipefile'] 1972*4882a593Smuzhiyun if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile): 1973*4882a593Smuzhiyun # This should always be true if recipefile is set, but just in case 1974*4882a593Smuzhiyun preservedir(os.path.dirname(recipefile)) 1975*4882a593Smuzhiyun # We don't automatically create this dir next to appends, but the user can 1976*4882a593Smuzhiyun preservedir(os.path.join(config.workspace_path, 'appends', pn)) 1977*4882a593Smuzhiyun 1978*4882a593Smuzhiyun srctreebase = workspace[pn]['srctreebase'] 1979*4882a593Smuzhiyun if os.path.isdir(srctreebase): 1980*4882a593Smuzhiyun if os.listdir(srctreebase): 1981*4882a593Smuzhiyun if remove_work: 1982*4882a593Smuzhiyun logger.info('-r argument used on %s, removing source tree.' 1983*4882a593Smuzhiyun ' You will lose any unsaved work' %pn) 1984*4882a593Smuzhiyun shutil.rmtree(srctreebase) 1985*4882a593Smuzhiyun else: 1986*4882a593Smuzhiyun # We don't want to risk wiping out any work in progress 1987*4882a593Smuzhiyun logger.info('Leaving source tree %s as-is; if you no ' 1988*4882a593Smuzhiyun 'longer need it then please delete it manually' 1989*4882a593Smuzhiyun % srctreebase) 1990*4882a593Smuzhiyun else: 1991*4882a593Smuzhiyun # This is unlikely, but if it's empty we can just remove it 1992*4882a593Smuzhiyun os.rmdir(srctreebase) 1993*4882a593Smuzhiyun 1994*4882a593Smuzhiyun clean_preferred_provider(pn, config.workspace_path) 1995*4882a593Smuzhiyun 1996*4882a593Smuzhiyundef reset(args, config, basepath, workspace): 1997*4882a593Smuzhiyun """Entry point for the devtool 'reset' subcommand""" 1998*4882a593Smuzhiyun import bb 1999*4882a593Smuzhiyun import shutil 2000*4882a593Smuzhiyun 2001*4882a593Smuzhiyun recipes = "" 2002*4882a593Smuzhiyun 2003*4882a593Smuzhiyun if args.recipename: 2004*4882a593Smuzhiyun if args.all: 2005*4882a593Smuzhiyun raise DevtoolError("Recipe cannot be specified if -a/--all is used") 2006*4882a593Smuzhiyun else: 2007*4882a593Smuzhiyun for recipe in args.recipename: 2008*4882a593Smuzhiyun check_workspace_recipe(workspace, recipe, checksrc=False) 2009*4882a593Smuzhiyun elif not args.all: 2010*4882a593Smuzhiyun raise DevtoolError("Recipe must be specified, or specify -a/--all to " 2011*4882a593Smuzhiyun "reset all recipes") 2012*4882a593Smuzhiyun if args.all: 2013*4882a593Smuzhiyun recipes = list(workspace.keys()) 2014*4882a593Smuzhiyun else: 2015*4882a593Smuzhiyun recipes = args.recipename 2016*4882a593Smuzhiyun 2017*4882a593Smuzhiyun _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace) 2018*4882a593Smuzhiyun 2019*4882a593Smuzhiyun return 0 2020*4882a593Smuzhiyun 2021*4882a593Smuzhiyun 2022*4882a593Smuzhiyundef _get_layer(layername, d): 2023*4882a593Smuzhiyun """Determine the base layer path for the specified layer name/path""" 2024*4882a593Smuzhiyun layerdirs = d.getVar('BBLAYERS').split() 2025*4882a593Smuzhiyun layers = {} # {basename: layer_paths} 2026*4882a593Smuzhiyun for p in layerdirs: 2027*4882a593Smuzhiyun bn = os.path.basename(p) 2028*4882a593Smuzhiyun if bn not in layers: 2029*4882a593Smuzhiyun layers[bn] = [p] 2030*4882a593Smuzhiyun else: 2031*4882a593Smuzhiyun layers[bn].append(p) 2032*4882a593Smuzhiyun # Provide some shortcuts 2033*4882a593Smuzhiyun if layername.lower() in ['oe-core', 'openembedded-core']: 2034*4882a593Smuzhiyun layername = 'meta' 2035*4882a593Smuzhiyun layer_paths = layers.get(layername, None) 2036*4882a593Smuzhiyun if not layer_paths: 2037*4882a593Smuzhiyun return os.path.abspath(layername) 2038*4882a593Smuzhiyun elif len(layer_paths) == 1: 2039*4882a593Smuzhiyun return os.path.abspath(layer_paths[0]) 2040*4882a593Smuzhiyun else: 2041*4882a593Smuzhiyun # multiple layers having the same base name 2042*4882a593Smuzhiyun logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0])) 2043*4882a593Smuzhiyun logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths)) 2044*4882a593Smuzhiyun return os.path.abspath(layer_paths[0]) 2045*4882a593Smuzhiyun 2046*4882a593Smuzhiyun 2047*4882a593Smuzhiyundef finish(args, config, basepath, workspace): 2048*4882a593Smuzhiyun """Entry point for the devtool 'finish' subcommand""" 2049*4882a593Smuzhiyun import bb 2050*4882a593Smuzhiyun import oe.recipeutils 2051*4882a593Smuzhiyun 2052*4882a593Smuzhiyun check_workspace_recipe(workspace, args.recipename) 2053*4882a593Smuzhiyun 2054*4882a593Smuzhiyun dry_run_suffix = ' (dry-run)' if args.dry_run else '' 2055*4882a593Smuzhiyun 2056*4882a593Smuzhiyun # Grab the equivalent of COREBASE without having to initialise tinfoil 2057*4882a593Smuzhiyun corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) 2058*4882a593Smuzhiyun 2059*4882a593Smuzhiyun srctree = workspace[args.recipename]['srctree'] 2060*4882a593Smuzhiyun check_git_repo_op(srctree, [corebasedir]) 2061*4882a593Smuzhiyun dirty = check_git_repo_dirty(srctree) 2062*4882a593Smuzhiyun if dirty: 2063*4882a593Smuzhiyun if args.force: 2064*4882a593Smuzhiyun logger.warning('Source tree is not clean, continuing as requested by -f/--force') 2065*4882a593Smuzhiyun else: 2066*4882a593Smuzhiyun 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) 2067*4882a593Smuzhiyun 2068*4882a593Smuzhiyun no_clean = args.no_clean 2069*4882a593Smuzhiyun remove_work=args.remove_work 2070*4882a593Smuzhiyun tinfoil = setup_tinfoil(basepath=basepath, tracking=True) 2071*4882a593Smuzhiyun try: 2072*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, args.recipename, True) 2073*4882a593Smuzhiyun if not rd: 2074*4882a593Smuzhiyun return 1 2075*4882a593Smuzhiyun 2076*4882a593Smuzhiyun destlayerdir = _get_layer(args.destination, tinfoil.config_data) 2077*4882a593Smuzhiyun recipefile = rd.getVar('FILE') 2078*4882a593Smuzhiyun recipedir = os.path.dirname(recipefile) 2079*4882a593Smuzhiyun origlayerdir = oe.recipeutils.find_layerdir(recipefile) 2080*4882a593Smuzhiyun 2081*4882a593Smuzhiyun if not os.path.isdir(destlayerdir): 2082*4882a593Smuzhiyun raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination) 2083*4882a593Smuzhiyun 2084*4882a593Smuzhiyun if os.path.abspath(destlayerdir) == config.workspace_path: 2085*4882a593Smuzhiyun raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination) 2086*4882a593Smuzhiyun 2087*4882a593Smuzhiyun # If it's an upgrade, grab the original path 2088*4882a593Smuzhiyun origpath = None 2089*4882a593Smuzhiyun origfilelist = None 2090*4882a593Smuzhiyun append = workspace[args.recipename]['bbappend'] 2091*4882a593Smuzhiyun with open(append, 'r') as f: 2092*4882a593Smuzhiyun for line in f: 2093*4882a593Smuzhiyun if line.startswith('# original_path:'): 2094*4882a593Smuzhiyun origpath = line.split(':')[1].strip() 2095*4882a593Smuzhiyun elif line.startswith('# original_files:'): 2096*4882a593Smuzhiyun origfilelist = line.split(':')[1].split() 2097*4882a593Smuzhiyun 2098*4882a593Smuzhiyun destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir) 2099*4882a593Smuzhiyun 2100*4882a593Smuzhiyun if origlayerdir == config.workspace_path: 2101*4882a593Smuzhiyun # Recipe file itself is in workspace, update it there first 2102*4882a593Smuzhiyun appendlayerdir = None 2103*4882a593Smuzhiyun origrelpath = None 2104*4882a593Smuzhiyun if origpath: 2105*4882a593Smuzhiyun origlayerpath = oe.recipeutils.find_layerdir(origpath) 2106*4882a593Smuzhiyun if origlayerpath: 2107*4882a593Smuzhiyun origrelpath = os.path.relpath(origpath, origlayerpath) 2108*4882a593Smuzhiyun destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath) 2109*4882a593Smuzhiyun if not destpath: 2110*4882a593Smuzhiyun raise DevtoolError("Unable to determine destination layer path - check that %s specifies an actual layer and %s/conf/layer.conf specifies BBFILES. You may also need to specify a more complete path." % (args.destination, destlayerdir)) 2111*4882a593Smuzhiyun # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases) 2112*4882a593Smuzhiyun layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()] 2113*4882a593Smuzhiyun if not os.path.abspath(destlayerbasedir) in layerdirs: 2114*4882a593Smuzhiyun bb.warn('Specified destination layer is not currently enabled in bblayers.conf, so the %s recipe will now be unavailable in your current configuration until you add the layer there' % args.recipename) 2115*4882a593Smuzhiyun 2116*4882a593Smuzhiyun elif destlayerdir == origlayerdir: 2117*4882a593Smuzhiyun # Same layer, update the original recipe 2118*4882a593Smuzhiyun appendlayerdir = None 2119*4882a593Smuzhiyun destpath = None 2120*4882a593Smuzhiyun else: 2121*4882a593Smuzhiyun # Create/update a bbappend in the specified layer 2122*4882a593Smuzhiyun appendlayerdir = destlayerdir 2123*4882a593Smuzhiyun destpath = None 2124*4882a593Smuzhiyun 2125*4882a593Smuzhiyun # Actually update the recipe / bbappend 2126*4882a593Smuzhiyun removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir) 2127*4882a593Smuzhiyun dry_run_output = None 2128*4882a593Smuzhiyun dry_run_outdir = None 2129*4882a593Smuzhiyun if args.dry_run: 2130*4882a593Smuzhiyun dry_run_output = tempfile.TemporaryDirectory(prefix='devtool') 2131*4882a593Smuzhiyun dry_run_outdir = dry_run_output.name 2132*4882a593Smuzhiyun updated, appendfile, removed = _update_recipe(args.recipename, workspace, rd, args.mode, appendlayerdir, wildcard_version=True, no_remove=False, no_report_remove=removing_original, initial_rev=args.initial_rev, dry_run_outdir=dry_run_outdir, no_overrides=args.no_overrides, force_patch_refresh=args.force_patch_refresh) 2133*4882a593Smuzhiyun removed = [os.path.relpath(pth, recipedir) for pth in removed] 2134*4882a593Smuzhiyun 2135*4882a593Smuzhiyun # Remove any old files in the case of an upgrade 2136*4882a593Smuzhiyun if removing_original: 2137*4882a593Smuzhiyun for fn in origfilelist: 2138*4882a593Smuzhiyun fnp = os.path.join(origpath, fn) 2139*4882a593Smuzhiyun if fn in removed or not os.path.exists(os.path.join(recipedir, fn)): 2140*4882a593Smuzhiyun logger.info('Removing file %s%s' % (fnp, dry_run_suffix)) 2141*4882a593Smuzhiyun if not args.dry_run: 2142*4882a593Smuzhiyun try: 2143*4882a593Smuzhiyun os.remove(fnp) 2144*4882a593Smuzhiyun except FileNotFoundError: 2145*4882a593Smuzhiyun pass 2146*4882a593Smuzhiyun 2147*4882a593Smuzhiyun if origlayerdir == config.workspace_path and destpath: 2148*4882a593Smuzhiyun # Recipe file itself is in the workspace - need to move it and any 2149*4882a593Smuzhiyun # associated files to the specified layer 2150*4882a593Smuzhiyun no_clean = True 2151*4882a593Smuzhiyun logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix)) 2152*4882a593Smuzhiyun for root, _, files in os.walk(recipedir): 2153*4882a593Smuzhiyun for fn in files: 2154*4882a593Smuzhiyun srcpath = os.path.join(root, fn) 2155*4882a593Smuzhiyun relpth = os.path.relpath(os.path.dirname(srcpath), recipedir) 2156*4882a593Smuzhiyun destdir = os.path.abspath(os.path.join(destpath, relpth)) 2157*4882a593Smuzhiyun destfp = os.path.join(destdir, fn) 2158*4882a593Smuzhiyun _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath) 2159*4882a593Smuzhiyun 2160*4882a593Smuzhiyun if dry_run_outdir: 2161*4882a593Smuzhiyun import difflib 2162*4882a593Smuzhiyun comparelist = [] 2163*4882a593Smuzhiyun for root, _, files in os.walk(dry_run_outdir): 2164*4882a593Smuzhiyun for fn in files: 2165*4882a593Smuzhiyun outf = os.path.join(root, fn) 2166*4882a593Smuzhiyun relf = os.path.relpath(outf, dry_run_outdir) 2167*4882a593Smuzhiyun logger.debug('dry-run: output file %s' % relf) 2168*4882a593Smuzhiyun if fn.endswith('.bb'): 2169*4882a593Smuzhiyun if origfilelist and origpath and destpath: 2170*4882a593Smuzhiyun # Need to match this up with the pre-upgrade recipe file 2171*4882a593Smuzhiyun for origf in origfilelist: 2172*4882a593Smuzhiyun if origf.endswith('.bb'): 2173*4882a593Smuzhiyun comparelist.append((os.path.abspath(os.path.join(origpath, origf)), 2174*4882a593Smuzhiyun outf, 2175*4882a593Smuzhiyun os.path.abspath(os.path.join(destpath, relf)))) 2176*4882a593Smuzhiyun break 2177*4882a593Smuzhiyun else: 2178*4882a593Smuzhiyun # Compare to the existing recipe 2179*4882a593Smuzhiyun comparelist.append((recipefile, outf, recipefile)) 2180*4882a593Smuzhiyun elif fn.endswith('.bbappend'): 2181*4882a593Smuzhiyun if appendfile: 2182*4882a593Smuzhiyun if os.path.exists(appendfile): 2183*4882a593Smuzhiyun comparelist.append((appendfile, outf, appendfile)) 2184*4882a593Smuzhiyun else: 2185*4882a593Smuzhiyun comparelist.append((None, outf, appendfile)) 2186*4882a593Smuzhiyun else: 2187*4882a593Smuzhiyun if destpath: 2188*4882a593Smuzhiyun recipedest = destpath 2189*4882a593Smuzhiyun elif appendfile: 2190*4882a593Smuzhiyun recipedest = os.path.dirname(appendfile) 2191*4882a593Smuzhiyun else: 2192*4882a593Smuzhiyun recipedest = os.path.dirname(recipefile) 2193*4882a593Smuzhiyun destfp = os.path.join(recipedest, relf) 2194*4882a593Smuzhiyun if os.path.exists(destfp): 2195*4882a593Smuzhiyun comparelist.append((destfp, outf, destfp)) 2196*4882a593Smuzhiyun output = '' 2197*4882a593Smuzhiyun for oldfile, newfile, newfileshow in comparelist: 2198*4882a593Smuzhiyun if oldfile: 2199*4882a593Smuzhiyun with open(oldfile, 'r') as f: 2200*4882a593Smuzhiyun oldlines = f.readlines() 2201*4882a593Smuzhiyun else: 2202*4882a593Smuzhiyun oldfile = '/dev/null' 2203*4882a593Smuzhiyun oldlines = [] 2204*4882a593Smuzhiyun with open(newfile, 'r') as f: 2205*4882a593Smuzhiyun newlines = f.readlines() 2206*4882a593Smuzhiyun if not newfileshow: 2207*4882a593Smuzhiyun newfileshow = newfile 2208*4882a593Smuzhiyun diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow) 2209*4882a593Smuzhiyun difflines = list(diff) 2210*4882a593Smuzhiyun if difflines: 2211*4882a593Smuzhiyun output += ''.join(difflines) 2212*4882a593Smuzhiyun if output: 2213*4882a593Smuzhiyun logger.info('Diff of changed files:\n%s' % output) 2214*4882a593Smuzhiyun finally: 2215*4882a593Smuzhiyun tinfoil.shutdown() 2216*4882a593Smuzhiyun 2217*4882a593Smuzhiyun # Everything else has succeeded, we can now reset 2218*4882a593Smuzhiyun if args.dry_run: 2219*4882a593Smuzhiyun logger.info('Resetting recipe (dry-run)') 2220*4882a593Smuzhiyun else: 2221*4882a593Smuzhiyun _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace) 2222*4882a593Smuzhiyun 2223*4882a593Smuzhiyun return 0 2224*4882a593Smuzhiyun 2225*4882a593Smuzhiyun 2226*4882a593Smuzhiyundef get_default_srctree(config, recipename=''): 2227*4882a593Smuzhiyun """Get the default srctree path""" 2228*4882a593Smuzhiyun srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path) 2229*4882a593Smuzhiyun if recipename: 2230*4882a593Smuzhiyun return os.path.join(srctreeparent, 'sources', recipename) 2231*4882a593Smuzhiyun else: 2232*4882a593Smuzhiyun return os.path.join(srctreeparent, 'sources') 2233*4882a593Smuzhiyun 2234*4882a593Smuzhiyundef register_commands(subparsers, context): 2235*4882a593Smuzhiyun """Register devtool subcommands from this plugin""" 2236*4882a593Smuzhiyun 2237*4882a593Smuzhiyun defsrctree = get_default_srctree(context.config) 2238*4882a593Smuzhiyun parser_add = subparsers.add_parser('add', help='Add a new recipe', 2239*4882a593Smuzhiyun description='Adds a new recipe to the workspace to build a specified source tree. Can optionally fetch a remote URI and unpack it to create the source tree.', 2240*4882a593Smuzhiyun group='starting', order=100) 2241*4882a593Smuzhiyun parser_add.add_argument('recipename', nargs='?', help='Name for new recipe to add (just name - no version, path or extension). If not specified, will attempt to auto-detect it.') 2242*4882a593Smuzhiyun parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree) 2243*4882a593Smuzhiyun parser_add.add_argument('fetchuri', nargs='?', help='Fetch the specified URI and extract it to create the source tree') 2244*4882a593Smuzhiyun group = parser_add.add_mutually_exclusive_group() 2245*4882a593Smuzhiyun group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true") 2246*4882a593Smuzhiyun group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true") 2247*4882a593Smuzhiyun parser_add.add_argument('--fetch', '-f', help='Fetch the specified URI and extract it to create the source tree (deprecated - pass as positional argument instead)', metavar='URI') 2248*4882a593Smuzhiyun parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true") 2249*4882a593Smuzhiyun parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)') 2250*4882a593Smuzhiyun parser_add.add_argument('--no-git', '-g', help='If fetching source, do not set up source tree as a git repository', action="store_true") 2251*4882a593Smuzhiyun group = parser_add.add_mutually_exclusive_group() 2252*4882a593Smuzhiyun group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)') 2253*4882a593Smuzhiyun group.add_argument('--autorev', '-a', help='When fetching from a git repository, set SRCREV in the recipe to a floating revision instead of fixed', action="store_true") 2254*4882a593Smuzhiyun parser_add.add_argument('--srcbranch', '-B', help='Branch in source repository if fetching from an SCM such as git (default master)') 2255*4882a593Smuzhiyun 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') 2256*4882a593Smuzhiyun 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') 2257*4882a593Smuzhiyun parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR') 2258*4882a593Smuzhiyun parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true") 2259*4882a593Smuzhiyun parser_add.add_argument('--provides', '-p', help='Specify an alias for the item provided by the recipe. E.g. virtual/libgl') 2260*4882a593Smuzhiyun parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup) 2261*4882a593Smuzhiyun 2262*4882a593Smuzhiyun parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe', 2263*4882a593Smuzhiyun description='Sets up the build environment to modify the source for an existing recipe. The default behaviour is to extract the source being fetched by the recipe into a git tree so you can work on it; alternatively if you already have your own pre-prepared source tree you can specify -n/--no-extract.', 2264*4882a593Smuzhiyun group='starting', order=90) 2265*4882a593Smuzhiyun parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)') 2266*4882a593Smuzhiyun parser_modify.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree) 2267*4882a593Smuzhiyun parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend') 2268*4882a593Smuzhiyun group = parser_modify.add_mutually_exclusive_group() 2269*4882a593Smuzhiyun group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)') 2270*4882a593Smuzhiyun group.add_argument('--no-extract', '-n', action="store_true", help='Do not extract source, expect it to exist') 2271*4882a593Smuzhiyun group = parser_modify.add_mutually_exclusive_group() 2272*4882a593Smuzhiyun group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true") 2273*4882a593Smuzhiyun group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true") 2274*4882a593Smuzhiyun parser_modify.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (when not using -n/--no-extract) (default "%(default)s")') 2275*4882a593Smuzhiyun parser_modify.add_argument('--no-overrides', '-O', action="store_true", help='Do not create branches for other override configurations') 2276*4882a593Smuzhiyun parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true") 2277*4882a593Smuzhiyun parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup) 2278*4882a593Smuzhiyun 2279*4882a593Smuzhiyun parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe', 2280*4882a593Smuzhiyun description='Extracts the source for an existing recipe', 2281*4882a593Smuzhiyun group='advanced') 2282*4882a593Smuzhiyun parser_extract.add_argument('recipename', help='Name of recipe to extract the source for') 2283*4882a593Smuzhiyun parser_extract.add_argument('srctree', help='Path to where to extract the source tree') 2284*4882a593Smuzhiyun parser_extract.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (default "%(default)s")') 2285*4882a593Smuzhiyun parser_extract.add_argument('--no-overrides', '-O', action="store_true", help='Do not create branches for other override configurations') 2286*4882a593Smuzhiyun parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)') 2287*4882a593Smuzhiyun parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup) 2288*4882a593Smuzhiyun 2289*4882a593Smuzhiyun parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe', 2290*4882a593Smuzhiyun description='Synchronize the previously extracted source tree for an existing recipe', 2291*4882a593Smuzhiyun formatter_class=argparse.ArgumentDefaultsHelpFormatter, 2292*4882a593Smuzhiyun group='advanced') 2293*4882a593Smuzhiyun parser_sync.add_argument('recipename', help='Name of recipe to sync the source for') 2294*4882a593Smuzhiyun parser_sync.add_argument('srctree', help='Path to the source tree') 2295*4882a593Smuzhiyun parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout') 2296*4882a593Smuzhiyun parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)') 2297*4882a593Smuzhiyun parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup) 2298*4882a593Smuzhiyun 2299*4882a593Smuzhiyun parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace', 2300*4882a593Smuzhiyun description='Renames the recipe file for a recipe in the workspace, changing the name or version part or both, ensuring that all references within the workspace are updated at the same time. Only works when the recipe file itself is in the workspace, e.g. after devtool add. Particularly useful when devtool add did not automatically determine the correct name.', 2301*4882a593Smuzhiyun group='working', order=10) 2302*4882a593Smuzhiyun parser_rename.add_argument('recipename', help='Current name of recipe to rename') 2303*4882a593Smuzhiyun parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)') 2304*4882a593Smuzhiyun parser_rename.add_argument('--version', '-V', help='Change the version (NOTE: this does not change the version fetched by the recipe, just the version in the recipe file name)') 2305*4882a593Smuzhiyun parser_rename.add_argument('--no-srctree', '-s', action='store_true', help='Do not rename the source tree directory (if the default source tree path has been used) - keeping the old name may be desirable if there are internal/other external references to this path') 2306*4882a593Smuzhiyun parser_rename.set_defaults(func=rename) 2307*4882a593Smuzhiyun 2308*4882a593Smuzhiyun parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe', 2309*4882a593Smuzhiyun description='Applies changes from external source tree to a recipe (updating/adding/removing patches as necessary, or by updating SRCREV). Note that these changes need to have been committed to the git repository in order to be recognised.', 2310*4882a593Smuzhiyun group='working', order=-90) 2311*4882a593Smuzhiyun parser_update_recipe.add_argument('recipename', help='Name of recipe to update') 2312*4882a593Smuzhiyun parser_update_recipe.add_argument('--mode', '-m', choices=['patch', 'srcrev', 'auto'], default='auto', help='Update mode (where %(metavar)s is %(choices)s; default is %(default)s)', metavar='MODE') 2313*4882a593Smuzhiyun parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches') 2314*4882a593Smuzhiyun parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR') 2315*4882a593Smuzhiyun parser_update_recipe.add_argument('--wildcard-version', '-w', help='In conjunction with -a/--append, use a wildcard to make the bbappend apply to any recipe version', action='store_true') 2316*4882a593Smuzhiyun parser_update_recipe.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update') 2317*4882a593Smuzhiyun parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)') 2318*4882a593Smuzhiyun parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)') 2319*4882a593Smuzhiyun parser_update_recipe.add_argument('--force-patch-refresh', action="store_true", help='Update patches in the layer even if they have not been modified (useful for refreshing patch context)') 2320*4882a593Smuzhiyun parser_update_recipe.set_defaults(func=update_recipe) 2321*4882a593Smuzhiyun 2322*4882a593Smuzhiyun parser_status = subparsers.add_parser('status', help='Show workspace status', 2323*4882a593Smuzhiyun description='Lists recipes currently in your workspace and the paths to their respective external source trees', 2324*4882a593Smuzhiyun group='info', order=100) 2325*4882a593Smuzhiyun parser_status.set_defaults(func=status) 2326*4882a593Smuzhiyun 2327*4882a593Smuzhiyun parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace', 2328*4882a593Smuzhiyun description='Removes the specified recipe(s) from your workspace (resetting its state back to that defined by the metadata).', 2329*4882a593Smuzhiyun group='working', order=-100) 2330*4882a593Smuzhiyun parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset') 2331*4882a593Smuzhiyun parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)') 2332*4882a593Smuzhiyun parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output') 2333*4882a593Smuzhiyun parser_reset.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory along with append') 2334*4882a593Smuzhiyun parser_reset.set_defaults(func=reset) 2335*4882a593Smuzhiyun 2336*4882a593Smuzhiyun parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace', 2337*4882a593Smuzhiyun description='Pushes any committed changes to the specified recipe to the specified layer and removes it from your workspace. Roughly equivalent to an update-recipe followed by reset, except the update-recipe step will do the "right thing" depending on the recipe and the destination layer specified. Note that your changes must have been committed to the git repository in order to be recognised.', 2338*4882a593Smuzhiyun group='working', order=-100) 2339*4882a593Smuzhiyun parser_finish.add_argument('recipename', help='Recipe to finish') 2340*4882a593Smuzhiyun parser_finish.add_argument('destination', help='Layer/path to put recipe into. Can be the name of a layer configured in your bblayers.conf, the path to the base of a layer, or a partial path inside a layer. %(prog)s will attempt to complete the path based on the layer\'s structure.') 2341*4882a593Smuzhiyun parser_finish.add_argument('--mode', '-m', choices=['patch', 'srcrev', 'auto'], default='auto', help='Update mode (where %(metavar)s is %(choices)s; default is %(default)s)', metavar='MODE') 2342*4882a593Smuzhiyun parser_finish.add_argument('--initial-rev', help='Override starting revision for patches') 2343*4882a593Smuzhiyun parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository') 2344*4882a593Smuzhiyun parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace') 2345*4882a593Smuzhiyun parser_finish.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output') 2346*4882a593Smuzhiyun parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)') 2347*4882a593Smuzhiyun parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)') 2348*4882a593Smuzhiyun parser_finish.add_argument('--force-patch-refresh', action="store_true", help='Update patches in the layer even if they have not been modified (useful for refreshing patch context)') 2349*4882a593Smuzhiyun parser_finish.set_defaults(func=finish) 2350