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