1# Development tool - source extraction helper class
2#
3# NOTE: this class is intended for use by devtool and should not be
4# inherited manually.
5#
6# Copyright (C) 2014-2017 Intel Corporation
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21
22DEVTOOL_TEMPDIR ?= ""
23DEVTOOL_PATCH_SRCDIR = "${DEVTOOL_TEMPDIR}/patchworkdir"
24
25
26python() {
27    tempdir = d.getVar('DEVTOOL_TEMPDIR')
28
29    if not tempdir:
30        bb.fatal('devtool-source class is for internal use by devtool only')
31
32    # Make a subdir so we guard against WORKDIR==S
33    workdir = os.path.join(tempdir, 'workdir')
34    d.setVar('WORKDIR', workdir)
35    if not d.getVar('S').startswith(workdir):
36        # Usually a shared workdir recipe (kernel, gcc)
37        # Try to set a reasonable default
38        if bb.data.inherits_class('kernel', d):
39            d.setVar('S', '${WORKDIR}/source')
40        else:
41            d.setVar('S', '${WORKDIR}/%s' % os.path.basename(d.getVar('S')))
42    if bb.data.inherits_class('kernel', d):
43        # We don't want to move the source to STAGING_KERNEL_DIR here
44        d.setVar('STAGING_KERNEL_DIR', '${S}')
45
46    d.setVar('STAMPS_DIR', os.path.join(tempdir, 'stamps'))
47    d.setVar('T', os.path.join(tempdir, 'temp'))
48
49    # Hook in pre/postfuncs
50    is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
51    if is_kernel_yocto:
52        unpacktask = 'do_kernel_checkout'
53        d.appendVarFlag('do_configure', 'postfuncs', ' devtool_post_configure')
54    else:
55        unpacktask = 'do_unpack'
56    d.appendVarFlag(unpacktask, 'postfuncs', ' devtool_post_unpack')
57    d.prependVarFlag('do_patch', 'prefuncs', ' devtool_pre_patch')
58    d.appendVarFlag('do_patch', 'postfuncs', ' devtool_post_patch')
59
60    # NOTE: in order for the patch stuff to be fully functional,
61    # PATCHTOOL and PATCH_COMMIT_FUNCTIONS need to be set; we can't
62    # do that here because we can't guarantee the order of the anonymous
63    # functions, so it gets done in the bbappend we create.
64}
65
66
67python devtool_post_unpack() {
68    import oe.recipeutils
69    import shutil
70    sys.path.insert(0, os.path.join(d.getVar('COREBASE'), 'scripts', 'lib'))
71    import scriptutils
72    from devtool import setup_git_repo
73
74    tempdir = d.getVar('DEVTOOL_TEMPDIR')
75    workdir = d.getVar('WORKDIR')
76    srcsubdir = d.getVar('S')
77
78    def _move_file(src, dst):
79        """Move a file. Creates all the directory components of destination path."""
80        dst_d = os.path.dirname(dst)
81        if dst_d:
82            bb.utils.mkdirhier(dst_d)
83        shutil.move(src, dst)
84
85    def _ls_tree(directory):
86        """Recursive listing of files in a directory"""
87        ret = []
88        for root, dirs, files in os.walk(directory):
89            ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
90                        fname in files])
91        return ret
92
93    is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
94    # Move local source files into separate subdir
95    recipe_patches = [os.path.basename(patch) for patch in
96                        oe.recipeutils.get_recipe_patches(d)]
97    local_files = oe.recipeutils.get_recipe_local_files(d)
98
99    if is_kernel_yocto:
100        for key in [f for f in local_files if f.endswith('scc')]:
101            with open(local_files[key], 'r') as sccfile:
102                for l in sccfile:
103                    line = l.split()
104                    if line and line[0] in ('kconf', 'patch'):
105                        cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
106                        if cfg not in local_files.values():
107                            local_files[line[-1]] = cfg
108                            shutil.copy2(cfg, workdir)
109
110    # Ignore local files with subdir={BP}
111    srcabspath = os.path.abspath(srcsubdir)
112    local_files = [fname for fname in local_files if
113                    os.path.exists(os.path.join(workdir, fname)) and
114                    (srcabspath == workdir or not
115                    os.path.join(workdir, fname).startswith(srcabspath +
116                        os.sep))]
117    if local_files:
118        for fname in local_files:
119            _move_file(os.path.join(workdir, fname),
120                        os.path.join(tempdir, 'oe-local-files', fname))
121        with open(os.path.join(tempdir, 'oe-local-files', '.gitignore'),
122                    'w') as f:
123            f.write('# Ignore local files, by default. Remove this file '
124                    'if you want to commit the directory to Git\n*\n')
125
126    if srcsubdir == workdir:
127        # Find non-patch non-local sources that were "unpacked" to srctree
128        # directory
129        src_files = [fname for fname in _ls_tree(workdir) if
130                        os.path.basename(fname) not in recipe_patches]
131        srcsubdir = d.getVar('DEVTOOL_PATCH_SRCDIR')
132        # Move source files to S
133        for path in src_files:
134            _move_file(os.path.join(workdir, path),
135                        os.path.join(srcsubdir, path))
136    elif os.path.dirname(srcsubdir) != workdir:
137        # Handle if S is set to a subdirectory of the source
138        srcsubdir = os.path.join(workdir, os.path.relpath(srcsubdir, workdir).split(os.sep)[0])
139
140    scriptutils.git_convert_standalone_clone(srcsubdir)
141
142    # Make sure that srcsubdir exists
143    bb.utils.mkdirhier(srcsubdir)
144    if not os.listdir(srcsubdir):
145        bb.warn("No source unpacked to S - either the %s recipe "
146                "doesn't use any source or the correct source "
147                "directory could not be determined" % d.getVar('PN'))
148
149    devbranch = d.getVar('DEVTOOL_DEVBRANCH')
150    setup_git_repo(srcsubdir, d.getVar('PV'), devbranch, d=d)
151
152    (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srcsubdir)
153    initial_rev = stdout.rstrip()
154    with open(os.path.join(tempdir, 'initial_rev'), 'w') as f:
155        f.write(initial_rev)
156
157    with open(os.path.join(tempdir, 'srcsubdir'), 'w') as f:
158        f.write(srcsubdir)
159}
160
161python devtool_pre_patch() {
162    if d.getVar('S') == d.getVar('WORKDIR'):
163        d.setVar('S', '${DEVTOOL_PATCH_SRCDIR}')
164}
165
166python devtool_post_patch() {
167    import shutil
168    tempdir = d.getVar('DEVTOOL_TEMPDIR')
169    with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
170        srcsubdir = f.read()
171    with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
172        initial_rev = f.read()
173
174    def rm_patches():
175        patches_dir = os.path.join(srcsubdir, 'patches')
176        if os.path.exists(patches_dir):
177            shutil.rmtree(patches_dir)
178        # Restore any "patches" directory that was actually part of the source tree
179        try:
180            bb.process.run('git checkout -- patches', cwd=srcsubdir)
181        except bb.process.ExecutionError:
182            pass
183
184    extra_overrides = d.getVar('DEVTOOL_EXTRA_OVERRIDES')
185    if extra_overrides:
186        extra_overrides = set(extra_overrides.split(':'))
187        devbranch = d.getVar('DEVTOOL_DEVBRANCH')
188        default_overrides = d.getVar('OVERRIDES').split(':')
189        no_overrides = []
190        # First, we may have some overrides that are referred to in the recipe set in
191        # our configuration, so we need to make a branch that excludes those
192        for override in default_overrides:
193            if override not in extra_overrides:
194                no_overrides.append(override)
195        if default_overrides != no_overrides:
196            # Some overrides are active in the current configuration, so
197            # we need to create a branch where none of the overrides are active
198            bb.process.run('git checkout %s -b devtool-no-overrides' % initial_rev, cwd=srcsubdir)
199            # Run do_patch function with the override applied
200            localdata = bb.data.createCopy(d)
201            localdata.setVar('OVERRIDES', ':'.join(no_overrides))
202            bb.build.exec_func('do_patch', localdata)
203            rm_patches()
204            # Now we need to reconcile the dev branch with the no-overrides one
205            # (otherwise we'd likely be left with identical commits that have different hashes)
206            bb.process.run('git checkout %s' % devbranch, cwd=srcsubdir)
207            bb.process.run('git rebase devtool-no-overrides', cwd=srcsubdir)
208        else:
209            bb.process.run('git checkout %s -b devtool-no-overrides' % devbranch, cwd=srcsubdir)
210
211        for override in extra_overrides:
212            localdata = bb.data.createCopy(d)
213            if override in default_overrides:
214                bb.process.run('git branch devtool-override-%s %s' % (override, devbranch), cwd=srcsubdir)
215            else:
216                # Reset back to the initial commit on a new branch
217                bb.process.run('git checkout %s -b devtool-override-%s' % (initial_rev, override), cwd=srcsubdir)
218                # Run do_patch function with the override applied
219                localdata.appendVar('OVERRIDES', ':%s' % override)
220                bb.build.exec_func('do_patch', localdata)
221                rm_patches()
222                # Now we need to reconcile the new branch with the no-overrides one
223                # (otherwise we'd likely be left with identical commits that have different hashes)
224                bb.process.run('git rebase devtool-no-overrides', cwd=srcsubdir)
225        bb.process.run('git checkout %s' % devbranch, cwd=srcsubdir)
226    bb.process.run('git tag -f devtool-patched', cwd=srcsubdir)
227}
228
229python devtool_post_configure() {
230    import shutil
231    tempdir = d.getVar('DEVTOOL_TEMPDIR')
232    shutil.copy2(os.path.join(d.getVar('B'), '.config'), tempdir)
233}
234