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