1*4882a593Smuzhiyun# Copyright (C) 2012 Linux Foundation 2*4882a593Smuzhiyun# Author: Richard Purdie 3*4882a593Smuzhiyun# Some code and influence taken from srctree.bbclass: 4*4882a593Smuzhiyun# Copyright (C) 2009 Chris Larson <clarson@kergoth.com> 5*4882a593Smuzhiyun# Released under the MIT license (see COPYING.MIT for the terms) 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# externalsrc.bbclass enables use of an existing source tree, usually external to 8*4882a593Smuzhiyun# the build system to build a piece of software rather than the usual fetch/unpack/patch 9*4882a593Smuzhiyun# process. 10*4882a593Smuzhiyun# 11*4882a593Smuzhiyun# To use, add externalsrc to the global inherit and set EXTERNALSRC to point at the 12*4882a593Smuzhiyun# directory you want to use containing the sources e.g. from local.conf for a recipe 13*4882a593Smuzhiyun# called "myrecipe" you would do: 14*4882a593Smuzhiyun# 15*4882a593Smuzhiyun# INHERIT += "externalsrc" 16*4882a593Smuzhiyun# EXTERNALSRC:pn-myrecipe = "/path/to/my/source/tree" 17*4882a593Smuzhiyun# 18*4882a593Smuzhiyun# In order to make this class work for both target and native versions (or with 19*4882a593Smuzhiyun# multilibs/cross or other BBCLASSEXTEND variants), B is set to point to a separate 20*4882a593Smuzhiyun# directory under the work directory (split source and build directories). This is 21*4882a593Smuzhiyun# the default, but the build directory can be set to the source directory if 22*4882a593Smuzhiyun# circumstances dictate by setting EXTERNALSRC_BUILD to the same value, e.g.: 23*4882a593Smuzhiyun# 24*4882a593Smuzhiyun# EXTERNALSRC_BUILD:pn-myrecipe = "/path/to/my/source/tree" 25*4882a593Smuzhiyun# 26*4882a593Smuzhiyun 27*4882a593SmuzhiyunSRCTREECOVEREDTASKS ?= "do_patch do_unpack do_fetch" 28*4882a593SmuzhiyunEXTERNALSRC_SYMLINKS ?= "oe-workdir:${WORKDIR} oe-logs:${T}" 29*4882a593Smuzhiyun 30*4882a593Smuzhiyunpython () { 31*4882a593Smuzhiyun externalsrc = d.getVar('EXTERNALSRC') 32*4882a593Smuzhiyun externalsrcbuild = d.getVar('EXTERNALSRC_BUILD') 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun if externalsrc and not externalsrc.startswith("/"): 35*4882a593Smuzhiyun bb.error("EXTERNALSRC must be an absolute path") 36*4882a593Smuzhiyun if externalsrcbuild and not externalsrcbuild.startswith("/"): 37*4882a593Smuzhiyun bb.error("EXTERNALSRC_BUILD must be an absolute path") 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun # If this is the base recipe and EXTERNALSRC is set for it or any of its 40*4882a593Smuzhiyun # derivatives, then enable BB_DONT_CACHE to force the recipe to always be 41*4882a593Smuzhiyun # re-parsed so that the file-checksums function for do_compile is run every 42*4882a593Smuzhiyun # time. 43*4882a593Smuzhiyun bpn = d.getVar('BPN') 44*4882a593Smuzhiyun classextend = (d.getVar('BBCLASSEXTEND') or '').split() 45*4882a593Smuzhiyun if bpn == d.getVar('PN') or not classextend: 46*4882a593Smuzhiyun if (externalsrc or 47*4882a593Smuzhiyun ('native' in classextend and 48*4882a593Smuzhiyun d.getVar('EXTERNALSRC:pn-%s-native' % bpn)) or 49*4882a593Smuzhiyun ('nativesdk' in classextend and 50*4882a593Smuzhiyun d.getVar('EXTERNALSRC:pn-nativesdk-%s' % bpn)) or 51*4882a593Smuzhiyun ('cross' in classextend and 52*4882a593Smuzhiyun d.getVar('EXTERNALSRC:pn-%s-cross' % bpn))): 53*4882a593Smuzhiyun d.setVar('BB_DONT_CACHE', '1') 54*4882a593Smuzhiyun 55*4882a593Smuzhiyun if externalsrc: 56*4882a593Smuzhiyun import oe.recipeutils 57*4882a593Smuzhiyun import oe.path 58*4882a593Smuzhiyun 59*4882a593Smuzhiyun d.setVar('S', externalsrc) 60*4882a593Smuzhiyun if externalsrcbuild: 61*4882a593Smuzhiyun d.setVar('B', externalsrcbuild) 62*4882a593Smuzhiyun else: 63*4882a593Smuzhiyun d.setVar('B', '${WORKDIR}/${BPN}-${PV}') 64*4882a593Smuzhiyun 65*4882a593Smuzhiyun local_srcuri = [] 66*4882a593Smuzhiyun fetch = bb.fetch2.Fetch((d.getVar('SRC_URI') or '').split(), d) 67*4882a593Smuzhiyun for url in fetch.urls: 68*4882a593Smuzhiyun url_data = fetch.ud[url] 69*4882a593Smuzhiyun parm = url_data.parm 70*4882a593Smuzhiyun if (url_data.type == 'file' or 71*4882a593Smuzhiyun url_data.type == 'npmsw' or url_data.type == 'crate' or 72*4882a593Smuzhiyun 'type' in parm and parm['type'] == 'kmeta'): 73*4882a593Smuzhiyun local_srcuri.append(url) 74*4882a593Smuzhiyun 75*4882a593Smuzhiyun d.setVar('SRC_URI', ' '.join(local_srcuri)) 76*4882a593Smuzhiyun 77*4882a593Smuzhiyun # Dummy value because the default function can't be called with blank SRC_URI 78*4882a593Smuzhiyun d.setVar('SRCPV', '999') 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun if d.getVar('CONFIGUREOPT_DEPTRACK') == '--disable-dependency-tracking': 81*4882a593Smuzhiyun d.setVar('CONFIGUREOPT_DEPTRACK', '') 82*4882a593Smuzhiyun 83*4882a593Smuzhiyun tasks = filter(lambda k: d.getVarFlag(k, "task"), d.keys()) 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun for task in tasks: 86*4882a593Smuzhiyun if task.endswith("_setscene"): 87*4882a593Smuzhiyun # sstate is never going to work for external source trees, disable it 88*4882a593Smuzhiyun bb.build.deltask(task, d) 89*4882a593Smuzhiyun elif os.path.realpath(d.getVar('S')) == os.path.realpath(d.getVar('B')): 90*4882a593Smuzhiyun # Since configure will likely touch ${S}, ensure only we lock so one task has access at a time 91*4882a593Smuzhiyun d.appendVarFlag(task, "lockfiles", " ${S}/singletask.lock") 92*4882a593Smuzhiyun 93*4882a593Smuzhiyun for v in d.keys(): 94*4882a593Smuzhiyun cleandirs = d.getVarFlag(v, "cleandirs", False) 95*4882a593Smuzhiyun if cleandirs: 96*4882a593Smuzhiyun # We do not want our source to be wiped out, ever (kernel.bbclass does this for do_clean) 97*4882a593Smuzhiyun cleandirs = oe.recipeutils.split_var_value(cleandirs) 98*4882a593Smuzhiyun setvalue = False 99*4882a593Smuzhiyun for cleandir in cleandirs[:]: 100*4882a593Smuzhiyun if oe.path.is_path_parent(externalsrc, d.expand(cleandir)): 101*4882a593Smuzhiyun cleandirs.remove(cleandir) 102*4882a593Smuzhiyun setvalue = True 103*4882a593Smuzhiyun if setvalue: 104*4882a593Smuzhiyun d.setVarFlag(v, 'cleandirs', ' '.join(cleandirs)) 105*4882a593Smuzhiyun 106*4882a593Smuzhiyun fetch_tasks = ['do_fetch', 'do_unpack'] 107*4882a593Smuzhiyun # If we deltask do_patch, there's no dependency to ensure do_unpack gets run, so add one 108*4882a593Smuzhiyun # Note that we cannot use d.appendVarFlag() here because deps is expected to be a list object, not a string 109*4882a593Smuzhiyun d.setVarFlag('do_configure', 'deps', (d.getVarFlag('do_configure', 'deps', False) or []) + ['do_unpack']) 110*4882a593Smuzhiyun 111*4882a593Smuzhiyun for task in d.getVar("SRCTREECOVEREDTASKS").split(): 112*4882a593Smuzhiyun if local_srcuri and task in fetch_tasks: 113*4882a593Smuzhiyun continue 114*4882a593Smuzhiyun bb.build.deltask(task, d) 115*4882a593Smuzhiyun if task == 'do_unpack': 116*4882a593Smuzhiyun # The reproducible build create_source_date_epoch_stamp function must 117*4882a593Smuzhiyun # be run after the source is available and before the 118*4882a593Smuzhiyun # do_deploy_source_date_epoch task. In the normal case, it's attached 119*4882a593Smuzhiyun # to do_unpack as a postfuncs, but since we removed do_unpack (above) 120*4882a593Smuzhiyun # we need to move the function elsewhere. The easiest thing to do is 121*4882a593Smuzhiyun # move it into the prefuncs of the do_deploy_source_date_epoch task. 122*4882a593Smuzhiyun # This is safe, as externalsrc runs with the source already unpacked. 123*4882a593Smuzhiyun d.prependVarFlag('do_deploy_source_date_epoch', 'prefuncs', 'create_source_date_epoch_stamp ') 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun d.prependVarFlag('do_compile', 'prefuncs', "externalsrc_compile_prefunc ") 126*4882a593Smuzhiyun d.prependVarFlag('do_configure', 'prefuncs', "externalsrc_configure_prefunc ") 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun d.setVarFlag('do_compile', 'file-checksums', '${@srctree_hash_files(d)}') 129*4882a593Smuzhiyun d.setVarFlag('do_configure', 'file-checksums', '${@srctree_configure_hash_files(d)}') 130*4882a593Smuzhiyun 131*4882a593Smuzhiyun # We don't want the workdir to go away 132*4882a593Smuzhiyun d.appendVar('RM_WORK_EXCLUDE', ' ' + d.getVar('PN')) 133*4882a593Smuzhiyun 134*4882a593Smuzhiyun bb.build.addtask('do_buildclean', 135*4882a593Smuzhiyun 'do_clean' if d.getVar('S') == d.getVar('B') else None, 136*4882a593Smuzhiyun None, d) 137*4882a593Smuzhiyun 138*4882a593Smuzhiyun # If B=S the same builddir is used even for different architectures. 139*4882a593Smuzhiyun # Thus, use a shared CONFIGURESTAMPFILE and STAMP directory so that 140*4882a593Smuzhiyun # change of do_configure task hash is correctly detected and stamps are 141*4882a593Smuzhiyun # invalidated if e.g. MACHINE changes. 142*4882a593Smuzhiyun if d.getVar('S') == d.getVar('B'): 143*4882a593Smuzhiyun configstamp = '${TMPDIR}/work-shared/${PN}/${EXTENDPE}${PV}-${PR}/configure.sstate' 144*4882a593Smuzhiyun d.setVar('CONFIGURESTAMPFILE', configstamp) 145*4882a593Smuzhiyun d.setVar('STAMP', '${STAMPS_DIR}/work-shared/${PN}/${EXTENDPE}${PV}-${PR}') 146*4882a593Smuzhiyun d.setVar('STAMPCLEAN', '${STAMPS_DIR}/work-shared/${PN}/*-*') 147*4882a593Smuzhiyun} 148*4882a593Smuzhiyun 149*4882a593Smuzhiyunpython externalsrc_configure_prefunc() { 150*4882a593Smuzhiyun s_dir = d.getVar('S') 151*4882a593Smuzhiyun # Create desired symlinks 152*4882a593Smuzhiyun symlinks = (d.getVar('EXTERNALSRC_SYMLINKS') or '').split() 153*4882a593Smuzhiyun newlinks = [] 154*4882a593Smuzhiyun for symlink in symlinks: 155*4882a593Smuzhiyun symsplit = symlink.split(':', 1) 156*4882a593Smuzhiyun lnkfile = os.path.join(s_dir, symsplit[0]) 157*4882a593Smuzhiyun target = d.expand(symsplit[1]) 158*4882a593Smuzhiyun if len(symsplit) > 1: 159*4882a593Smuzhiyun if os.path.islink(lnkfile): 160*4882a593Smuzhiyun # Link already exists, leave it if it points to the right location already 161*4882a593Smuzhiyun if os.readlink(lnkfile) == target: 162*4882a593Smuzhiyun continue 163*4882a593Smuzhiyun os.unlink(lnkfile) 164*4882a593Smuzhiyun elif os.path.exists(lnkfile): 165*4882a593Smuzhiyun # File/dir exists with same name as link, just leave it alone 166*4882a593Smuzhiyun continue 167*4882a593Smuzhiyun os.symlink(target, lnkfile) 168*4882a593Smuzhiyun newlinks.append(symsplit[0]) 169*4882a593Smuzhiyun # Hide the symlinks from git 170*4882a593Smuzhiyun try: 171*4882a593Smuzhiyun git_exclude_file = os.path.join(s_dir, '.git/info/exclude') 172*4882a593Smuzhiyun if os.path.exists(git_exclude_file): 173*4882a593Smuzhiyun with open(git_exclude_file, 'r+') as efile: 174*4882a593Smuzhiyun elines = efile.readlines() 175*4882a593Smuzhiyun for link in newlinks: 176*4882a593Smuzhiyun if link in elines or '/'+link in elines: 177*4882a593Smuzhiyun continue 178*4882a593Smuzhiyun efile.write('/' + link + '\n') 179*4882a593Smuzhiyun except IOError as ioe: 180*4882a593Smuzhiyun bb.note('Failed to hide EXTERNALSRC_SYMLINKS from git') 181*4882a593Smuzhiyun} 182*4882a593Smuzhiyun 183*4882a593Smuzhiyunpython externalsrc_compile_prefunc() { 184*4882a593Smuzhiyun # Make it obvious that this is happening, since forgetting about it could lead to much confusion 185*4882a593Smuzhiyun bb.plain('NOTE: %s: compiling from external source tree %s' % (d.getVar('PN'), d.getVar('EXTERNALSRC'))) 186*4882a593Smuzhiyun} 187*4882a593Smuzhiyun 188*4882a593Smuzhiyundo_buildclean[dirs] = "${S} ${B}" 189*4882a593Smuzhiyundo_buildclean[nostamp] = "1" 190*4882a593Smuzhiyundo_buildclean[doc] = "Call 'make clean' or equivalent in ${B}" 191*4882a593Smuzhiyunexternalsrc_do_buildclean() { 192*4882a593Smuzhiyun if [ -e Makefile -o -e makefile -o -e GNUmakefile ]; then 193*4882a593Smuzhiyun rm -f ${@' '.join([x.split(':')[0] for x in (d.getVar('EXTERNALSRC_SYMLINKS') or '').split()])} 194*4882a593Smuzhiyun if [ "${CLEANBROKEN}" != "1" ]; then 195*4882a593Smuzhiyun oe_runmake clean || die "make failed" 196*4882a593Smuzhiyun fi 197*4882a593Smuzhiyun else 198*4882a593Smuzhiyun bbnote "nothing to do - no makefile found" 199*4882a593Smuzhiyun fi 200*4882a593Smuzhiyun} 201*4882a593Smuzhiyun 202*4882a593Smuzhiyundef srctree_hash_files(d, srcdir=None): 203*4882a593Smuzhiyun import shutil 204*4882a593Smuzhiyun import subprocess 205*4882a593Smuzhiyun import tempfile 206*4882a593Smuzhiyun import hashlib 207*4882a593Smuzhiyun 208*4882a593Smuzhiyun s_dir = srcdir or d.getVar('EXTERNALSRC') 209*4882a593Smuzhiyun git_dir = None 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun try: 212*4882a593Smuzhiyun git_dir = os.path.join(s_dir, 213*4882a593Smuzhiyun subprocess.check_output(['git', '-C', s_dir, 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL).decode("utf-8").rstrip()) 214*4882a593Smuzhiyun top_git_dir = os.path.join(d.getVar("TOPDIR"), 215*4882a593Smuzhiyun subprocess.check_output(['git', '-C', d.getVar("TOPDIR"), 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL).decode("utf-8").rstrip()) 216*4882a593Smuzhiyun if git_dir == top_git_dir: 217*4882a593Smuzhiyun git_dir = None 218*4882a593Smuzhiyun except subprocess.CalledProcessError: 219*4882a593Smuzhiyun pass 220*4882a593Smuzhiyun 221*4882a593Smuzhiyun ret = " " 222*4882a593Smuzhiyun if git_dir is not None: 223*4882a593Smuzhiyun oe_hash_file = os.path.join(git_dir, 'oe-devtool-tree-sha1-%s' % d.getVar('PN')) 224*4882a593Smuzhiyun with tempfile.NamedTemporaryFile(prefix='oe-devtool-index') as tmp_index: 225*4882a593Smuzhiyun # Clone index 226*4882a593Smuzhiyun shutil.copyfile(os.path.join(git_dir, 'index'), tmp_index.name) 227*4882a593Smuzhiyun # Update our custom index 228*4882a593Smuzhiyun env = os.environ.copy() 229*4882a593Smuzhiyun env['GIT_INDEX_FILE'] = tmp_index.name 230*4882a593Smuzhiyun subprocess.check_output(['git', 'add', '-A', '.'], cwd=s_dir, env=env) 231*4882a593Smuzhiyun git_sha1 = subprocess.check_output(['git', 'write-tree'], cwd=s_dir, env=env).decode("utf-8") 232*4882a593Smuzhiyun if os.path.exists(os.path.join(s_dir, ".gitmodules")) and os.path.getsize(os.path.join(s_dir, ".gitmodules")) > 0: 233*4882a593Smuzhiyun submodule_helper = subprocess.check_output(["git", "config", "--file", ".gitmodules", "--get-regexp", "path"], cwd=s_dir, env=env).decode("utf-8") 234*4882a593Smuzhiyun for line in submodule_helper.splitlines(): 235*4882a593Smuzhiyun module_dir = os.path.join(s_dir, line.rsplit(maxsplit=1)[1]) 236*4882a593Smuzhiyun if os.path.isdir(module_dir): 237*4882a593Smuzhiyun proc = subprocess.Popen(['git', 'add', '-A', '.'], cwd=module_dir, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 238*4882a593Smuzhiyun proc.communicate() 239*4882a593Smuzhiyun proc = subprocess.Popen(['git', 'write-tree'], cwd=module_dir, env=env, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) 240*4882a593Smuzhiyun stdout, _ = proc.communicate() 241*4882a593Smuzhiyun git_sha1 += stdout.decode("utf-8") 242*4882a593Smuzhiyun sha1 = hashlib.sha1(git_sha1.encode("utf-8")).hexdigest() 243*4882a593Smuzhiyun with open(oe_hash_file, 'w') as fobj: 244*4882a593Smuzhiyun fobj.write(sha1) 245*4882a593Smuzhiyun ret = oe_hash_file + ':True' 246*4882a593Smuzhiyun else: 247*4882a593Smuzhiyun ret = s_dir + '/*:True' 248*4882a593Smuzhiyun return ret 249*4882a593Smuzhiyun 250*4882a593Smuzhiyundef srctree_configure_hash_files(d): 251*4882a593Smuzhiyun """ 252*4882a593Smuzhiyun Get the list of files that should trigger do_configure to re-execute, 253*4882a593Smuzhiyun based on the value of CONFIGURE_FILES 254*4882a593Smuzhiyun """ 255*4882a593Smuzhiyun in_files = (d.getVar('CONFIGURE_FILES') or '').split() 256*4882a593Smuzhiyun out_items = [] 257*4882a593Smuzhiyun search_files = [] 258*4882a593Smuzhiyun for entry in in_files: 259*4882a593Smuzhiyun if entry.startswith('/'): 260*4882a593Smuzhiyun out_items.append('%s:%s' % (entry, os.path.exists(entry))) 261*4882a593Smuzhiyun else: 262*4882a593Smuzhiyun search_files.append(entry) 263*4882a593Smuzhiyun if search_files: 264*4882a593Smuzhiyun s_dir = d.getVar('EXTERNALSRC') 265*4882a593Smuzhiyun for root, _, files in os.walk(s_dir): 266*4882a593Smuzhiyun for f in files: 267*4882a593Smuzhiyun if f in search_files: 268*4882a593Smuzhiyun out_items.append('%s:True' % os.path.join(root, f)) 269*4882a593Smuzhiyun return ' '.join(out_items) 270*4882a593Smuzhiyun 271*4882a593SmuzhiyunEXPORT_FUNCTIONS do_buildclean 272