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