1eb8dc403SDave Cobbley#!/usr/bin/env python3
2eb8dc403SDave Cobbley
3eb8dc403SDave Cobbley# Development tool - utility functions for plugins
4eb8dc403SDave Cobbley#
5eb8dc403SDave Cobbley# Copyright (C) 2014 Intel Corporation
6eb8dc403SDave Cobbley#
7c342db35SBrad Bishop# SPDX-License-Identifier: GPL-2.0-only
8eb8dc403SDave Cobbley#
9eb8dc403SDave Cobbley"""Devtool plugins module"""
10eb8dc403SDave Cobbley
11eb8dc403SDave Cobbleyimport os
12eb8dc403SDave Cobbleyimport sys
13eb8dc403SDave Cobbleyimport subprocess
14eb8dc403SDave Cobbleyimport logging
15eb8dc403SDave Cobbleyimport re
16eb8dc403SDave Cobbleyimport codecs
17eb8dc403SDave Cobbley
18eb8dc403SDave Cobbleylogger = logging.getLogger('devtool')
19eb8dc403SDave Cobbley
20eb8dc403SDave Cobbleyclass DevtoolError(Exception):
21eb8dc403SDave Cobbley    """Exception for handling devtool errors"""
22eb8dc403SDave Cobbley    def __init__(self, message, exitcode=1):
23eb8dc403SDave Cobbley        super(DevtoolError, self).__init__(message)
24eb8dc403SDave Cobbley        self.exitcode = exitcode
25eb8dc403SDave Cobbley
26eb8dc403SDave Cobbley
27eb8dc403SDave Cobbleydef exec_build_env_command(init_path, builddir, cmd, watch=False, **options):
28eb8dc403SDave Cobbley    """Run a program in bitbake build context"""
29eb8dc403SDave Cobbley    import bb
30eb8dc403SDave Cobbley    if not 'cwd' in options:
31eb8dc403SDave Cobbley        options["cwd"] = builddir
32eb8dc403SDave Cobbley    if init_path:
33eb8dc403SDave Cobbley        # As the OE init script makes use of BASH_SOURCE to determine OEROOT,
34eb8dc403SDave Cobbley        # and can't determine it when running under dash, we need to set
35eb8dc403SDave Cobbley        # the executable to bash to correctly set things up
36eb8dc403SDave Cobbley        if not 'executable' in options:
37eb8dc403SDave Cobbley            options['executable'] = 'bash'
38eb8dc403SDave Cobbley        logger.debug('Executing command: "%s" using init path %s' % (cmd, init_path))
39eb8dc403SDave Cobbley        init_prefix = '. %s %s > /dev/null && ' % (init_path, builddir)
40eb8dc403SDave Cobbley    else:
41eb8dc403SDave Cobbley        logger.debug('Executing command "%s"' % cmd)
42eb8dc403SDave Cobbley        init_prefix = ''
43eb8dc403SDave Cobbley    if watch:
44eb8dc403SDave Cobbley        if sys.stdout.isatty():
45eb8dc403SDave Cobbley            # Fool bitbake into thinking it's outputting to a terminal (because it is, indirectly)
46eb8dc403SDave Cobbley            cmd = 'script -e -q -c "%s" /dev/null' % cmd
47eb8dc403SDave Cobbley        return exec_watch('%s%s' % (init_prefix, cmd), **options)
48eb8dc403SDave Cobbley    else:
49eb8dc403SDave Cobbley        return bb.process.run('%s%s' % (init_prefix, cmd), **options)
50eb8dc403SDave Cobbley
51eb8dc403SDave Cobbleydef exec_watch(cmd, **options):
52eb8dc403SDave Cobbley    """Run program with stdout shown on sys.stdout"""
53eb8dc403SDave Cobbley    import bb
54eb8dc403SDave Cobbley    if isinstance(cmd, str) and not "shell" in options:
55eb8dc403SDave Cobbley        options["shell"] = True
56eb8dc403SDave Cobbley
57eb8dc403SDave Cobbley    process = subprocess.Popen(
58eb8dc403SDave Cobbley        cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **options
59eb8dc403SDave Cobbley    )
60eb8dc403SDave Cobbley
61eb8dc403SDave Cobbley    reader = codecs.getreader('utf-8')(process.stdout)
62eb8dc403SDave Cobbley    buf = ''
63eb8dc403SDave Cobbley    while True:
64eb8dc403SDave Cobbley        out = reader.read(1, 1)
65eb8dc403SDave Cobbley        if out:
66eb8dc403SDave Cobbley            sys.stdout.write(out)
67eb8dc403SDave Cobbley            sys.stdout.flush()
68eb8dc403SDave Cobbley            buf += out
69eb8dc403SDave Cobbley        elif out == '' and process.poll() != None:
70eb8dc403SDave Cobbley            break
71eb8dc403SDave Cobbley
72eb8dc403SDave Cobbley    if process.returncode != 0:
73eb8dc403SDave Cobbley        raise bb.process.ExecutionError(cmd, process.returncode, buf, None)
74eb8dc403SDave Cobbley
75eb8dc403SDave Cobbley    return buf, None
76eb8dc403SDave Cobbley
77eb8dc403SDave Cobbleydef exec_fakeroot(d, cmd, **kwargs):
78eb8dc403SDave Cobbley    """Run a command under fakeroot (pseudo, in fact) so that it picks up the appropriate file permissions"""
79eb8dc403SDave Cobbley    # Grab the command and check it actually exists
80eb8dc403SDave Cobbley    fakerootcmd = d.getVar('FAKEROOTCMD')
81*73bd93f1SPatrick Williams    fakerootenv = d.getVar('FAKEROOTENV')
82*73bd93f1SPatrick Williams    exec_fakeroot_no_d(fakerootcmd, fakerootenv, cmd, kwargs)
83*73bd93f1SPatrick Williams
84*73bd93f1SPatrick Williamsdef exec_fakeroot_no_d(fakerootcmd, fakerootenv, cmd, **kwargs):
85eb8dc403SDave Cobbley    if not os.path.exists(fakerootcmd):
86eb8dc403SDave Cobbley        logger.error('pseudo executable %s could not be found - have you run a build yet? pseudo-native should install this and if you have run any build then that should have been built')
87eb8dc403SDave Cobbley        return 2
88eb8dc403SDave Cobbley    # Set up the appropriate environment
89eb8dc403SDave Cobbley    newenv = dict(os.environ)
90eb8dc403SDave Cobbley    for varvalue in fakerootenv.split():
91eb8dc403SDave Cobbley        if '=' in varvalue:
92eb8dc403SDave Cobbley            splitval = varvalue.split('=', 1)
93eb8dc403SDave Cobbley            newenv[splitval[0]] = splitval[1]
94eb8dc403SDave Cobbley    return subprocess.call("%s %s" % (fakerootcmd, cmd), env=newenv, **kwargs)
95eb8dc403SDave Cobbley
96eb8dc403SDave Cobbleydef setup_tinfoil(config_only=False, basepath=None, tracking=False):
97eb8dc403SDave Cobbley    """Initialize tinfoil api from bitbake"""
98eb8dc403SDave Cobbley    import scriptpath
99eb8dc403SDave Cobbley    orig_cwd = os.path.abspath(os.curdir)
100eb8dc403SDave Cobbley    try:
101eb8dc403SDave Cobbley        if basepath:
102eb8dc403SDave Cobbley            os.chdir(basepath)
103eb8dc403SDave Cobbley        bitbakepath = scriptpath.add_bitbake_lib_path()
104eb8dc403SDave Cobbley        if not bitbakepath:
105eb8dc403SDave Cobbley            logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
106eb8dc403SDave Cobbley            sys.exit(1)
107eb8dc403SDave Cobbley
108eb8dc403SDave Cobbley        import bb.tinfoil
109eb8dc403SDave Cobbley        tinfoil = bb.tinfoil.Tinfoil(tracking=tracking)
110eb8dc403SDave Cobbley        try:
111eb8dc403SDave Cobbley            tinfoil.logger.setLevel(logger.getEffectiveLevel())
112eb8dc403SDave Cobbley            tinfoil.prepare(config_only)
113eb8dc403SDave Cobbley        except bb.tinfoil.TinfoilUIException:
114eb8dc403SDave Cobbley            tinfoil.shutdown()
115eb8dc403SDave Cobbley            raise DevtoolError('Failed to start bitbake environment')
116eb8dc403SDave Cobbley        except:
117eb8dc403SDave Cobbley            tinfoil.shutdown()
118eb8dc403SDave Cobbley            raise
119eb8dc403SDave Cobbley    finally:
120eb8dc403SDave Cobbley        os.chdir(orig_cwd)
121eb8dc403SDave Cobbley    return tinfoil
122eb8dc403SDave Cobbley
123eb8dc403SDave Cobbleydef parse_recipe(config, tinfoil, pn, appends, filter_workspace=True):
124eb8dc403SDave Cobbley    """Parse the specified recipe"""
125eb8dc403SDave Cobbley    try:
126eb8dc403SDave Cobbley        recipefile = tinfoil.get_recipe_file(pn)
127eb8dc403SDave Cobbley    except bb.providers.NoProvider as e:
128eb8dc403SDave Cobbley        logger.error(str(e))
129eb8dc403SDave Cobbley        return None
130eb8dc403SDave Cobbley    if appends:
131eb8dc403SDave Cobbley        append_files = tinfoil.get_file_appends(recipefile)
132eb8dc403SDave Cobbley        if filter_workspace:
133eb8dc403SDave Cobbley            # Filter out appends from the workspace
134eb8dc403SDave Cobbley            append_files = [path for path in append_files if
135eb8dc403SDave Cobbley                            not path.startswith(config.workspace_path)]
136eb8dc403SDave Cobbley    else:
137eb8dc403SDave Cobbley        append_files = None
138eb8dc403SDave Cobbley    try:
139eb8dc403SDave Cobbley        rd = tinfoil.parse_recipe_file(recipefile, appends, append_files)
140eb8dc403SDave Cobbley    except Exception as e:
141eb8dc403SDave Cobbley        logger.error(str(e))
142eb8dc403SDave Cobbley        return None
143eb8dc403SDave Cobbley    return rd
144eb8dc403SDave Cobbley
145eb8dc403SDave Cobbleydef check_workspace_recipe(workspace, pn, checksrc=True, bbclassextend=False):
146eb8dc403SDave Cobbley    """
147eb8dc403SDave Cobbley    Check that a recipe is in the workspace and (optionally) that source
148eb8dc403SDave Cobbley    is present.
149eb8dc403SDave Cobbley    """
150eb8dc403SDave Cobbley
151eb8dc403SDave Cobbley    workspacepn = pn
152eb8dc403SDave Cobbley
153eb8dc403SDave Cobbley    for recipe, value in workspace.items():
154eb8dc403SDave Cobbley        if recipe == pn:
155eb8dc403SDave Cobbley            break
156eb8dc403SDave Cobbley        if bbclassextend:
157eb8dc403SDave Cobbley            recipefile = value['recipefile']
158eb8dc403SDave Cobbley            if recipefile:
159eb8dc403SDave Cobbley                targets = get_bbclassextend_targets(recipefile, recipe)
160eb8dc403SDave Cobbley                if pn in targets:
161eb8dc403SDave Cobbley                    workspacepn = recipe
162eb8dc403SDave Cobbley                    break
163eb8dc403SDave Cobbley    else:
164eb8dc403SDave Cobbley        raise DevtoolError("No recipe named '%s' in your workspace" % pn)
165eb8dc403SDave Cobbley
166eb8dc403SDave Cobbley    if checksrc:
167eb8dc403SDave Cobbley        srctree = workspace[workspacepn]['srctree']
168eb8dc403SDave Cobbley        if not os.path.exists(srctree):
169eb8dc403SDave Cobbley            raise DevtoolError("Source tree %s for recipe %s does not exist" % (srctree, workspacepn))
170eb8dc403SDave Cobbley        if not os.listdir(srctree):
171eb8dc403SDave Cobbley            raise DevtoolError("Source tree %s for recipe %s is empty" % (srctree, workspacepn))
172eb8dc403SDave Cobbley
173eb8dc403SDave Cobbley    return workspacepn
174eb8dc403SDave Cobbley
175eb8dc403SDave Cobbleydef use_external_build(same_dir, no_same_dir, d):
176eb8dc403SDave Cobbley    """
177eb8dc403SDave Cobbley    Determine if we should use B!=S (separate build and source directories) or not
178eb8dc403SDave Cobbley    """
179eb8dc403SDave Cobbley    b_is_s = True
180eb8dc403SDave Cobbley    if no_same_dir:
181eb8dc403SDave Cobbley        logger.info('Using separate build directory since --no-same-dir specified')
182eb8dc403SDave Cobbley        b_is_s = False
183eb8dc403SDave Cobbley    elif same_dir:
184eb8dc403SDave Cobbley        logger.info('Using source tree as build directory since --same-dir specified')
185eb8dc403SDave Cobbley    elif bb.data.inherits_class('autotools-brokensep', d):
186eb8dc403SDave Cobbley        logger.info('Using source tree as build directory since recipe inherits autotools-brokensep')
187eb8dc403SDave Cobbley    elif os.path.abspath(d.getVar('B')) == os.path.abspath(d.getVar('S')):
188eb8dc403SDave Cobbley        logger.info('Using source tree as build directory since that would be the default for this recipe')
189eb8dc403SDave Cobbley    else:
190eb8dc403SDave Cobbley        b_is_s = False
191eb8dc403SDave Cobbley    return b_is_s
192eb8dc403SDave Cobbley
193eb8dc403SDave Cobbleydef setup_git_repo(repodir, version, devbranch, basetag='devtool-base', d=None):
194eb8dc403SDave Cobbley    """
195eb8dc403SDave Cobbley    Set up the git repository for the source tree
196eb8dc403SDave Cobbley    """
197eb8dc403SDave Cobbley    import bb.process
198eb8dc403SDave Cobbley    import oe.patch
199eb8dc403SDave Cobbley    if not os.path.exists(os.path.join(repodir, '.git')):
200eb8dc403SDave Cobbley        bb.process.run('git init', cwd=repodir)
20119323693SBrad Bishop        bb.process.run('git config --local gc.autodetach 0', cwd=repodir)
2024ed12e16SAndrew Geissler        bb.process.run('git add -f -A .', cwd=repodir)
203eb8dc403SDave Cobbley        commit_cmd = ['git']
204eb8dc403SDave Cobbley        oe.patch.GitApplyTree.gitCommandUserOptions(commit_cmd, d=d)
205eb8dc403SDave Cobbley        commit_cmd += ['commit', '-q']
206eb8dc403SDave Cobbley        stdout, _ = bb.process.run('git status --porcelain', cwd=repodir)
207eb8dc403SDave Cobbley        if not stdout:
208eb8dc403SDave Cobbley            commit_cmd.append('--allow-empty')
209eb8dc403SDave Cobbley            commitmsg = "Initial empty commit with no upstream sources"
210eb8dc403SDave Cobbley        elif version:
211eb8dc403SDave Cobbley            commitmsg = "Initial commit from upstream at version %s" % version
212eb8dc403SDave Cobbley        else:
213eb8dc403SDave Cobbley            commitmsg = "Initial commit from upstream"
214eb8dc403SDave Cobbley        commit_cmd += ['-m', commitmsg]
215eb8dc403SDave Cobbley        bb.process.run(commit_cmd, cwd=repodir)
216eb8dc403SDave Cobbley
217eb8dc403SDave Cobbley    # Ensure singletask.lock (as used by externalsrc.bbclass) is ignored by git
2184c19ea12SAndrew Geissler    gitinfodir = os.path.join(repodir, '.git', 'info')
2194c19ea12SAndrew Geissler    try:
2204c19ea12SAndrew Geissler        os.mkdir(gitinfodir)
2214c19ea12SAndrew Geissler    except FileExistsError:
2224c19ea12SAndrew Geissler        pass
223eb8dc403SDave Cobbley    excludes = []
2244c19ea12SAndrew Geissler    excludefile = os.path.join(gitinfodir, 'exclude')
225eb8dc403SDave Cobbley    try:
226eb8dc403SDave Cobbley        with open(excludefile, 'r') as f:
227eb8dc403SDave Cobbley            excludes = f.readlines()
228eb8dc403SDave Cobbley    except FileNotFoundError:
229eb8dc403SDave Cobbley        pass
230eb8dc403SDave Cobbley    if 'singletask.lock\n' not in excludes:
231eb8dc403SDave Cobbley        excludes.append('singletask.lock\n')
232eb8dc403SDave Cobbley    with open(excludefile, 'w') as f:
233eb8dc403SDave Cobbley        for line in excludes:
234eb8dc403SDave Cobbley            f.write(line)
235eb8dc403SDave Cobbley
236eb8dc403SDave Cobbley    bb.process.run('git checkout -b %s' % devbranch, cwd=repodir)
237eb8dc403SDave Cobbley    bb.process.run('git tag -f %s' % basetag, cwd=repodir)
238eb8dc403SDave Cobbley
239da295319SPatrick Williams    # if recipe unpacks another git repo inside S, we need to declare it as a regular git submodule now,
240da295319SPatrick Williams    # so we will be able to tag branches on it and extract patches when doing finish/update on the recipe
241da295319SPatrick Williams    stdout, _ = bb.process.run("git status --porcelain", cwd=repodir)
242da295319SPatrick Williams    found = False
243da295319SPatrick Williams    for line in stdout.splitlines():
244da295319SPatrick Williams        if line.endswith("/"):
245da295319SPatrick Williams            new_dir = line.split()[1]
246da295319SPatrick Williams            for root, dirs, files in os.walk(os.path.join(repodir, new_dir)):
247da295319SPatrick Williams                if ".git" in dirs + files:
248da295319SPatrick Williams                    (stdout, _) = bb.process.run('git remote', cwd=root)
249da295319SPatrick Williams                    remote = stdout.splitlines()[0]
250da295319SPatrick Williams                    (stdout, _) = bb.process.run('git remote get-url %s' % remote, cwd=root)
251da295319SPatrick Williams                    remote_url = stdout.splitlines()[0]
252da295319SPatrick Williams                    logger.error(os.path.relpath(os.path.join(root, ".."), root))
253da295319SPatrick Williams                    bb.process.run('git submodule add %s %s' % (remote_url, os.path.relpath(root, os.path.join(root, ".."))), cwd=os.path.join(root, ".."))
254da295319SPatrick Williams                    found = True
255da295319SPatrick Williams                if found:
256*73bd93f1SPatrick Williams                    oe.patch.GitApplyTree.commitIgnored("Add additional submodule from SRC_URI", dir=os.path.join(root, ".."), d=d)
257da295319SPatrick Williams                    found = False
258da295319SPatrick Williams    if os.path.exists(os.path.join(repodir, '.gitmodules')):
259da295319SPatrick Williams        bb.process.run('git submodule foreach --recursive  "git tag -f %s"' % basetag, cwd=repodir)
260da295319SPatrick Williams
261eb8dc403SDave Cobbleydef recipe_to_append(recipefile, config, wildcard=False):
262eb8dc403SDave Cobbley    """
263eb8dc403SDave Cobbley    Convert a recipe file to a bbappend file path within the workspace.
264eb8dc403SDave Cobbley    NOTE: if the bbappend already exists, you should be using
265eb8dc403SDave Cobbley    workspace[args.recipename]['bbappend'] instead of calling this
266eb8dc403SDave Cobbley    function.
267eb8dc403SDave Cobbley    """
268eb8dc403SDave Cobbley    appendname = os.path.splitext(os.path.basename(recipefile))[0]
269eb8dc403SDave Cobbley    if wildcard:
270eb8dc403SDave Cobbley        appendname = re.sub(r'_.*', '_%', appendname)
271eb8dc403SDave Cobbley    appendpath = os.path.join(config.workspace_path, 'appends')
272eb8dc403SDave Cobbley    appendfile = os.path.join(appendpath, appendname + '.bbappend')
273eb8dc403SDave Cobbley    return appendfile
274eb8dc403SDave Cobbley
275eb8dc403SDave Cobbleydef get_bbclassextend_targets(recipefile, pn):
276eb8dc403SDave Cobbley    """
277eb8dc403SDave Cobbley    Cheap function to get BBCLASSEXTEND and then convert that to the
278eb8dc403SDave Cobbley    list of targets that would result.
279eb8dc403SDave Cobbley    """
280eb8dc403SDave Cobbley    import bb.utils
281eb8dc403SDave Cobbley
282eb8dc403SDave Cobbley    values = {}
283eb8dc403SDave Cobbley    def get_bbclassextend_varfunc(varname, origvalue, op, newlines):
284eb8dc403SDave Cobbley        values[varname] = origvalue
285eb8dc403SDave Cobbley        return origvalue, None, 0, True
286eb8dc403SDave Cobbley    with open(recipefile, 'r') as f:
287eb8dc403SDave Cobbley        bb.utils.edit_metadata(f, ['BBCLASSEXTEND'], get_bbclassextend_varfunc)
288eb8dc403SDave Cobbley
289eb8dc403SDave Cobbley    targets = []
290eb8dc403SDave Cobbley    bbclassextend = values.get('BBCLASSEXTEND', '').split()
291eb8dc403SDave Cobbley    if bbclassextend:
292eb8dc403SDave Cobbley        for variant in bbclassextend:
293eb8dc403SDave Cobbley            if variant == 'nativesdk':
294eb8dc403SDave Cobbley                targets.append('%s-%s' % (variant, pn))
295eb8dc403SDave Cobbley            elif variant in ['native', 'cross', 'crosssdk']:
296eb8dc403SDave Cobbley                targets.append('%s-%s' % (pn, variant))
297eb8dc403SDave Cobbley    return targets
298eb8dc403SDave Cobbley
299eb8dc403SDave Cobbleydef replace_from_file(path, old, new):
300eb8dc403SDave Cobbley    """Replace strings on a file"""
301eb8dc403SDave Cobbley
302eb8dc403SDave Cobbley    def read_file(path):
303eb8dc403SDave Cobbley        data = None
304eb8dc403SDave Cobbley        with open(path) as f:
305eb8dc403SDave Cobbley            data = f.read()
306eb8dc403SDave Cobbley        return data
307eb8dc403SDave Cobbley
308eb8dc403SDave Cobbley    def write_file(path, data):
309eb8dc403SDave Cobbley        if data is None:
310eb8dc403SDave Cobbley            return
311eb8dc403SDave Cobbley        wdata = data.rstrip() + "\n"
312eb8dc403SDave Cobbley        with open(path, "w") as f:
313eb8dc403SDave Cobbley            f.write(wdata)
314eb8dc403SDave Cobbley
315eb8dc403SDave Cobbley    # In case old is None, return immediately
316eb8dc403SDave Cobbley    if old is None:
317eb8dc403SDave Cobbley        return
318eb8dc403SDave Cobbley    try:
319eb8dc403SDave Cobbley        rdata = read_file(path)
320eb8dc403SDave Cobbley    except IOError as e:
321eb8dc403SDave Cobbley        # if file does not exit, just quit, otherwise raise an exception
322eb8dc403SDave Cobbley        if e.errno == errno.ENOENT:
323eb8dc403SDave Cobbley            return
324eb8dc403SDave Cobbley        else:
325eb8dc403SDave Cobbley            raise
326eb8dc403SDave Cobbley
327eb8dc403SDave Cobbley    old_contents = rdata.splitlines()
328eb8dc403SDave Cobbley    new_contents = []
329eb8dc403SDave Cobbley    for old_content in old_contents:
330eb8dc403SDave Cobbley        try:
331eb8dc403SDave Cobbley            new_contents.append(old_content.replace(old, new))
332eb8dc403SDave Cobbley        except ValueError:
333eb8dc403SDave Cobbley            pass
334eb8dc403SDave Cobbley    write_file(path, "\n".join(new_contents))
335eb8dc403SDave Cobbley
336eb8dc403SDave Cobbley
337eb8dc403SDave Cobbleydef update_unlockedsigs(basepath, workspace, fixed_setup, extra=None):
338eb8dc403SDave Cobbley    """ This function will make unlocked-sigs.inc match the recipes in the
339eb8dc403SDave Cobbley    workspace plus any extras we want unlocked. """
340eb8dc403SDave Cobbley
341eb8dc403SDave Cobbley    if not fixed_setup:
342eb8dc403SDave Cobbley        # Only need to write this out within the eSDK
343eb8dc403SDave Cobbley        return
344eb8dc403SDave Cobbley
345eb8dc403SDave Cobbley    if not extra:
346eb8dc403SDave Cobbley        extra = []
347eb8dc403SDave Cobbley
348eb8dc403SDave Cobbley    confdir = os.path.join(basepath, 'conf')
349eb8dc403SDave Cobbley    unlockedsigs = os.path.join(confdir, 'unlocked-sigs.inc')
350eb8dc403SDave Cobbley
351eb8dc403SDave Cobbley    # Get current unlocked list if any
352eb8dc403SDave Cobbley    values = {}
353eb8dc403SDave Cobbley    def get_unlockedsigs_varfunc(varname, origvalue, op, newlines):
354eb8dc403SDave Cobbley        values[varname] = origvalue
355eb8dc403SDave Cobbley        return origvalue, None, 0, True
356eb8dc403SDave Cobbley    if os.path.exists(unlockedsigs):
357eb8dc403SDave Cobbley        with open(unlockedsigs, 'r') as f:
358eb8dc403SDave Cobbley            bb.utils.edit_metadata(f, ['SIGGEN_UNLOCKED_RECIPES'], get_unlockedsigs_varfunc)
359eb8dc403SDave Cobbley    unlocked = sorted(values.get('SIGGEN_UNLOCKED_RECIPES', []))
360eb8dc403SDave Cobbley
361eb8dc403SDave Cobbley    # If the new list is different to the current list, write it out
362eb8dc403SDave Cobbley    newunlocked = sorted(list(workspace.keys()) + extra)
363eb8dc403SDave Cobbley    if unlocked != newunlocked:
364eb8dc403SDave Cobbley        bb.utils.mkdirhier(confdir)
365eb8dc403SDave Cobbley        with open(unlockedsigs, 'w') as f:
366eb8dc403SDave Cobbley            f.write("# DO NOT MODIFY! YOUR CHANGES WILL BE LOST.\n" +
367eb8dc403SDave Cobbley                    "# This layer was created by the OpenEmbedded devtool" +
368eb8dc403SDave Cobbley                    " utility in order to\n" +
369eb8dc403SDave Cobbley                    "# contain recipes that are unlocked.\n")
370eb8dc403SDave Cobbley
371eb8dc403SDave Cobbley            f.write('SIGGEN_UNLOCKED_RECIPES += "\\\n')
372eb8dc403SDave Cobbley            for pn in newunlocked:
373eb8dc403SDave Cobbley                f.write('    ' + pn)
374eb8dc403SDave Cobbley            f.write('"')
375eb8dc403SDave Cobbley
376eb8dc403SDave Cobbleydef check_prerelease_version(ver, operation):
377eb8dc403SDave Cobbley    if 'pre' in ver or 'rc' in ver:
378eb8dc403SDave Cobbley        logger.warning('Version "%s" looks like a pre-release version. '
379eb8dc403SDave Cobbley                       'If that is the case, in order to ensure that the '
380eb8dc403SDave Cobbley                       'version doesn\'t appear to go backwards when you '
381eb8dc403SDave Cobbley                       'later upgrade to the final release version, it is '
382eb8dc403SDave Cobbley                       'recommmended that instead you use '
383eb8dc403SDave Cobbley                       '<current version>+<pre-release version> e.g. if '
384eb8dc403SDave Cobbley                       'upgrading from 1.9 to 2.0-rc2 use "1.9+2.0-rc2". '
385eb8dc403SDave Cobbley                       'If you prefer not to reset and re-try, you can change '
386eb8dc403SDave Cobbley                       'the version after %s succeeds using "devtool rename" '
387eb8dc403SDave Cobbley                       'with -V/--version.' % (ver, operation))
388eb8dc403SDave Cobbley
389eb8dc403SDave Cobbleydef check_git_repo_dirty(repodir):
390eb8dc403SDave Cobbley    """Check if a git repository is clean or not"""
391eb8dc403SDave Cobbley    stdout, _ = bb.process.run('git status --porcelain', cwd=repodir)
392eb8dc403SDave Cobbley    return stdout
393eb8dc403SDave Cobbley
394eb8dc403SDave Cobbleydef check_git_repo_op(srctree, ignoredirs=None):
395eb8dc403SDave Cobbley    """Check if a git repository is in the middle of a rebase"""
396eb8dc403SDave Cobbley    stdout, _ = bb.process.run('git rev-parse --show-toplevel', cwd=srctree)
397eb8dc403SDave Cobbley    topleveldir = stdout.strip()
398eb8dc403SDave Cobbley    if ignoredirs and topleveldir in ignoredirs:
399eb8dc403SDave Cobbley        return
400eb8dc403SDave Cobbley    gitdir = os.path.join(topleveldir, '.git')
401eb8dc403SDave Cobbley    if os.path.exists(os.path.join(gitdir, 'rebase-merge')):
402eb8dc403SDave Cobbley        raise DevtoolError("Source tree %s appears to be in the middle of a rebase - please resolve this first" % srctree)
403eb8dc403SDave Cobbley    if os.path.exists(os.path.join(gitdir, 'rebase-apply')):
404eb8dc403SDave Cobbley        raise DevtoolError("Source tree %s appears to be in the middle of 'git am' or 'git apply' - please resolve this first" % srctree)
405