xref: /OK3568_Linux_fs/yocto/scripts/lib/devtool/standard.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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