1*4882a593Smuzhiyun# Development tool - sdk-update command plugin 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (C) 2015-2016 Intel Corporation 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun 8*4882a593Smuzhiyunimport os 9*4882a593Smuzhiyunimport subprocess 10*4882a593Smuzhiyunimport logging 11*4882a593Smuzhiyunimport glob 12*4882a593Smuzhiyunimport shutil 13*4882a593Smuzhiyunimport errno 14*4882a593Smuzhiyunimport sys 15*4882a593Smuzhiyunimport tempfile 16*4882a593Smuzhiyunimport re 17*4882a593Smuzhiyunfrom devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError 18*4882a593Smuzhiyun 19*4882a593Smuzhiyunlogger = logging.getLogger('devtool') 20*4882a593Smuzhiyun 21*4882a593Smuzhiyundef parse_locked_sigs(sigfile_path): 22*4882a593Smuzhiyun """Return <pn:task>:<hash> dictionary""" 23*4882a593Smuzhiyun sig_dict = {} 24*4882a593Smuzhiyun with open(sigfile_path) as f: 25*4882a593Smuzhiyun lines = f.readlines() 26*4882a593Smuzhiyun for line in lines: 27*4882a593Smuzhiyun if ':' in line: 28*4882a593Smuzhiyun taskkey, _, hashval = line.rpartition(':') 29*4882a593Smuzhiyun sig_dict[taskkey.strip()] = hashval.split()[0] 30*4882a593Smuzhiyun return sig_dict 31*4882a593Smuzhiyun 32*4882a593Smuzhiyundef generate_update_dict(sigfile_new, sigfile_old): 33*4882a593Smuzhiyun """Return a dict containing <pn:task>:<hash> which indicates what need to be updated""" 34*4882a593Smuzhiyun update_dict = {} 35*4882a593Smuzhiyun sigdict_new = parse_locked_sigs(sigfile_new) 36*4882a593Smuzhiyun sigdict_old = parse_locked_sigs(sigfile_old) 37*4882a593Smuzhiyun for k in sigdict_new: 38*4882a593Smuzhiyun if k not in sigdict_old: 39*4882a593Smuzhiyun update_dict[k] = sigdict_new[k] 40*4882a593Smuzhiyun continue 41*4882a593Smuzhiyun if sigdict_new[k] != sigdict_old[k]: 42*4882a593Smuzhiyun update_dict[k] = sigdict_new[k] 43*4882a593Smuzhiyun continue 44*4882a593Smuzhiyun return update_dict 45*4882a593Smuzhiyun 46*4882a593Smuzhiyundef get_sstate_objects(update_dict, sstate_dir): 47*4882a593Smuzhiyun """Return a list containing sstate objects which are to be installed""" 48*4882a593Smuzhiyun sstate_objects = [] 49*4882a593Smuzhiyun for k in update_dict: 50*4882a593Smuzhiyun files = set() 51*4882a593Smuzhiyun hashval = update_dict[k] 52*4882a593Smuzhiyun p = sstate_dir + '/' + hashval[:2] + '/*' + hashval + '*.tgz' 53*4882a593Smuzhiyun files |= set(glob.glob(p)) 54*4882a593Smuzhiyun p = sstate_dir + '/*/' + hashval[:2] + '/*' + hashval + '*.tgz' 55*4882a593Smuzhiyun files |= set(glob.glob(p)) 56*4882a593Smuzhiyun files = list(files) 57*4882a593Smuzhiyun if len(files) == 1: 58*4882a593Smuzhiyun sstate_objects.extend(files) 59*4882a593Smuzhiyun elif len(files) > 1: 60*4882a593Smuzhiyun logger.error("More than one matching sstate object found for %s" % hashval) 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun return sstate_objects 63*4882a593Smuzhiyun 64*4882a593Smuzhiyundef mkdir(d): 65*4882a593Smuzhiyun try: 66*4882a593Smuzhiyun os.makedirs(d) 67*4882a593Smuzhiyun except OSError as e: 68*4882a593Smuzhiyun if e.errno != errno.EEXIST: 69*4882a593Smuzhiyun raise e 70*4882a593Smuzhiyun 71*4882a593Smuzhiyundef install_sstate_objects(sstate_objects, src_sdk, dest_sdk): 72*4882a593Smuzhiyun """Install sstate objects into destination SDK""" 73*4882a593Smuzhiyun sstate_dir = os.path.join(dest_sdk, 'sstate-cache') 74*4882a593Smuzhiyun if not os.path.exists(sstate_dir): 75*4882a593Smuzhiyun logger.error("Missing sstate-cache directory in %s, it might not be an extensible SDK." % dest_sdk) 76*4882a593Smuzhiyun raise 77*4882a593Smuzhiyun for sb in sstate_objects: 78*4882a593Smuzhiyun dst = sb.replace(src_sdk, dest_sdk) 79*4882a593Smuzhiyun destdir = os.path.dirname(dst) 80*4882a593Smuzhiyun mkdir(destdir) 81*4882a593Smuzhiyun logger.debug("Copying %s to %s" % (sb, dst)) 82*4882a593Smuzhiyun shutil.copy(sb, dst) 83*4882a593Smuzhiyun 84*4882a593Smuzhiyundef check_manifest(fn, basepath): 85*4882a593Smuzhiyun import bb.utils 86*4882a593Smuzhiyun changedfiles = [] 87*4882a593Smuzhiyun with open(fn, 'r') as f: 88*4882a593Smuzhiyun for line in f: 89*4882a593Smuzhiyun splitline = line.split() 90*4882a593Smuzhiyun if len(splitline) > 1: 91*4882a593Smuzhiyun chksum = splitline[0] 92*4882a593Smuzhiyun fpath = splitline[1] 93*4882a593Smuzhiyun curr_chksum = bb.utils.sha256_file(os.path.join(basepath, fpath)) 94*4882a593Smuzhiyun if chksum != curr_chksum: 95*4882a593Smuzhiyun logger.debug('File %s changed: old csum = %s, new = %s' % (os.path.join(basepath, fpath), curr_chksum, chksum)) 96*4882a593Smuzhiyun changedfiles.append(fpath) 97*4882a593Smuzhiyun return changedfiles 98*4882a593Smuzhiyun 99*4882a593Smuzhiyundef sdk_update(args, config, basepath, workspace): 100*4882a593Smuzhiyun """Entry point for devtool sdk-update command""" 101*4882a593Smuzhiyun updateserver = args.updateserver 102*4882a593Smuzhiyun if not updateserver: 103*4882a593Smuzhiyun updateserver = config.get('SDK', 'updateserver', '') 104*4882a593Smuzhiyun logger.debug("updateserver: %s" % updateserver) 105*4882a593Smuzhiyun 106*4882a593Smuzhiyun # Make sure we are using sdk-update from within SDK 107*4882a593Smuzhiyun logger.debug("basepath = %s" % basepath) 108*4882a593Smuzhiyun old_locked_sig_file_path = os.path.join(basepath, 'conf/locked-sigs.inc') 109*4882a593Smuzhiyun if not os.path.exists(old_locked_sig_file_path): 110*4882a593Smuzhiyun logger.error("Not using devtool's sdk-update command from within an extensible SDK. Please specify correct basepath via --basepath option") 111*4882a593Smuzhiyun return -1 112*4882a593Smuzhiyun else: 113*4882a593Smuzhiyun logger.debug("Found conf/locked-sigs.inc in %s" % basepath) 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun if not '://' in updateserver: 116*4882a593Smuzhiyun logger.error("Update server must be a URL") 117*4882a593Smuzhiyun return -1 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun layers_dir = os.path.join(basepath, 'layers') 120*4882a593Smuzhiyun conf_dir = os.path.join(basepath, 'conf') 121*4882a593Smuzhiyun 122*4882a593Smuzhiyun # Grab variable values 123*4882a593Smuzhiyun tinfoil = setup_tinfoil(config_only=True, basepath=basepath) 124*4882a593Smuzhiyun try: 125*4882a593Smuzhiyun stamps_dir = tinfoil.config_data.getVar('STAMPS_DIR') 126*4882a593Smuzhiyun sstate_mirrors = tinfoil.config_data.getVar('SSTATE_MIRRORS') 127*4882a593Smuzhiyun site_conf_version = tinfoil.config_data.getVar('SITE_CONF_VERSION') 128*4882a593Smuzhiyun finally: 129*4882a593Smuzhiyun tinfoil.shutdown() 130*4882a593Smuzhiyun 131*4882a593Smuzhiyun tmpsdk_dir = tempfile.mkdtemp() 132*4882a593Smuzhiyun try: 133*4882a593Smuzhiyun os.makedirs(os.path.join(tmpsdk_dir, 'conf')) 134*4882a593Smuzhiyun new_locked_sig_file_path = os.path.join(tmpsdk_dir, 'conf', 'locked-sigs.inc') 135*4882a593Smuzhiyun # Fetch manifest from server 136*4882a593Smuzhiyun tmpmanifest = os.path.join(tmpsdk_dir, 'conf', 'sdk-conf-manifest') 137*4882a593Smuzhiyun ret = subprocess.call("wget -q -O %s %s/conf/sdk-conf-manifest" % (tmpmanifest, updateserver), shell=True) 138*4882a593Smuzhiyun if ret != 0: 139*4882a593Smuzhiyun logger.error("Cannot dowload files from %s" % updateserver) 140*4882a593Smuzhiyun return ret 141*4882a593Smuzhiyun changedfiles = check_manifest(tmpmanifest, basepath) 142*4882a593Smuzhiyun if not changedfiles: 143*4882a593Smuzhiyun logger.info("Already up-to-date") 144*4882a593Smuzhiyun return 0 145*4882a593Smuzhiyun # Update metadata 146*4882a593Smuzhiyun logger.debug("Updating metadata via git ...") 147*4882a593Smuzhiyun #Check for the status before doing a fetch and reset 148*4882a593Smuzhiyun if os.path.exists(os.path.join(basepath, 'layers/.git')): 149*4882a593Smuzhiyun out = subprocess.check_output("git status --porcelain", shell=True, cwd=layers_dir) 150*4882a593Smuzhiyun if not out: 151*4882a593Smuzhiyun ret = subprocess.call("git fetch --all; git reset --hard @{u}", shell=True, cwd=layers_dir) 152*4882a593Smuzhiyun else: 153*4882a593Smuzhiyun logger.error("Failed to update metadata as there have been changes made to it. Aborting."); 154*4882a593Smuzhiyun logger.error("Changed files:\n%s" % out); 155*4882a593Smuzhiyun return -1 156*4882a593Smuzhiyun else: 157*4882a593Smuzhiyun ret = -1 158*4882a593Smuzhiyun if ret != 0: 159*4882a593Smuzhiyun ret = subprocess.call("git clone %s/layers/.git" % updateserver, shell=True, cwd=tmpsdk_dir) 160*4882a593Smuzhiyun if ret != 0: 161*4882a593Smuzhiyun logger.error("Updating metadata via git failed") 162*4882a593Smuzhiyun return ret 163*4882a593Smuzhiyun logger.debug("Updating conf files ...") 164*4882a593Smuzhiyun for changedfile in changedfiles: 165*4882a593Smuzhiyun ret = subprocess.call("wget -q -O %s %s/%s" % (changedfile, updateserver, changedfile), shell=True, cwd=tmpsdk_dir) 166*4882a593Smuzhiyun if ret != 0: 167*4882a593Smuzhiyun logger.error("Updating %s failed" % changedfile) 168*4882a593Smuzhiyun return ret 169*4882a593Smuzhiyun 170*4882a593Smuzhiyun # Check if UNINATIVE_CHECKSUM changed 171*4882a593Smuzhiyun uninative = False 172*4882a593Smuzhiyun if 'conf/local.conf' in changedfiles: 173*4882a593Smuzhiyun def read_uninative_checksums(fn): 174*4882a593Smuzhiyun chksumitems = [] 175*4882a593Smuzhiyun with open(fn, 'r') as f: 176*4882a593Smuzhiyun for line in f: 177*4882a593Smuzhiyun if line.startswith('UNINATIVE_CHECKSUM'): 178*4882a593Smuzhiyun splitline = re.split(r'[\[\]"\']', line) 179*4882a593Smuzhiyun if len(splitline) > 3: 180*4882a593Smuzhiyun chksumitems.append((splitline[1], splitline[3])) 181*4882a593Smuzhiyun return chksumitems 182*4882a593Smuzhiyun 183*4882a593Smuzhiyun oldsums = read_uninative_checksums(os.path.join(basepath, 'conf/local.conf')) 184*4882a593Smuzhiyun newsums = read_uninative_checksums(os.path.join(tmpsdk_dir, 'conf/local.conf')) 185*4882a593Smuzhiyun if oldsums != newsums: 186*4882a593Smuzhiyun uninative = True 187*4882a593Smuzhiyun for buildarch, chksum in newsums: 188*4882a593Smuzhiyun uninative_file = os.path.join('downloads', 'uninative', chksum, '%s-nativesdk-libc.tar.bz2' % buildarch) 189*4882a593Smuzhiyun mkdir(os.path.join(tmpsdk_dir, os.path.dirname(uninative_file))) 190*4882a593Smuzhiyun ret = subprocess.call("wget -q -O %s %s/%s" % (uninative_file, updateserver, uninative_file), shell=True, cwd=tmpsdk_dir) 191*4882a593Smuzhiyun 192*4882a593Smuzhiyun # Ok, all is well at this point - move everything over 193*4882a593Smuzhiyun tmplayers_dir = os.path.join(tmpsdk_dir, 'layers') 194*4882a593Smuzhiyun if os.path.exists(tmplayers_dir): 195*4882a593Smuzhiyun shutil.rmtree(layers_dir) 196*4882a593Smuzhiyun shutil.move(tmplayers_dir, layers_dir) 197*4882a593Smuzhiyun for changedfile in changedfiles: 198*4882a593Smuzhiyun destfile = os.path.join(basepath, changedfile) 199*4882a593Smuzhiyun os.remove(destfile) 200*4882a593Smuzhiyun shutil.move(os.path.join(tmpsdk_dir, changedfile), destfile) 201*4882a593Smuzhiyun os.remove(os.path.join(conf_dir, 'sdk-conf-manifest')) 202*4882a593Smuzhiyun shutil.move(tmpmanifest, conf_dir) 203*4882a593Smuzhiyun if uninative: 204*4882a593Smuzhiyun shutil.rmtree(os.path.join(basepath, 'downloads', 'uninative')) 205*4882a593Smuzhiyun shutil.move(os.path.join(tmpsdk_dir, 'downloads', 'uninative'), os.path.join(basepath, 'downloads')) 206*4882a593Smuzhiyun 207*4882a593Smuzhiyun if not sstate_mirrors: 208*4882a593Smuzhiyun with open(os.path.join(conf_dir, 'site.conf'), 'a') as f: 209*4882a593Smuzhiyun f.write('SCONF_VERSION = "%s"\n' % site_conf_version) 210*4882a593Smuzhiyun f.write('SSTATE_MIRRORS:append = " file://.* %s/sstate-cache/PATH"\n' % updateserver) 211*4882a593Smuzhiyun finally: 212*4882a593Smuzhiyun shutil.rmtree(tmpsdk_dir) 213*4882a593Smuzhiyun 214*4882a593Smuzhiyun if not args.skip_prepare: 215*4882a593Smuzhiyun # Find all potentially updateable tasks 216*4882a593Smuzhiyun sdk_update_targets = [] 217*4882a593Smuzhiyun tasks = ['do_populate_sysroot', 'do_packagedata'] 218*4882a593Smuzhiyun for root, _, files in os.walk(stamps_dir): 219*4882a593Smuzhiyun for fn in files: 220*4882a593Smuzhiyun if not '.sigdata.' in fn: 221*4882a593Smuzhiyun for task in tasks: 222*4882a593Smuzhiyun if '.%s.' % task in fn or '.%s_setscene.' % task in fn: 223*4882a593Smuzhiyun sdk_update_targets.append('%s:%s' % (os.path.basename(root), task)) 224*4882a593Smuzhiyun # Run bitbake command for the whole SDK 225*4882a593Smuzhiyun logger.info("Preparing build system... (This may take some time.)") 226*4882a593Smuzhiyun try: 227*4882a593Smuzhiyun exec_build_env_command(config.init_path, basepath, 'bitbake --setscene-only %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT) 228*4882a593Smuzhiyun output, _ = exec_build_env_command(config.init_path, basepath, 'bitbake -n %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT) 229*4882a593Smuzhiyun runlines = [] 230*4882a593Smuzhiyun for line in output.splitlines(): 231*4882a593Smuzhiyun if 'Running task ' in line: 232*4882a593Smuzhiyun runlines.append(line) 233*4882a593Smuzhiyun if runlines: 234*4882a593Smuzhiyun logger.error('Unexecuted tasks found in preparation log:\n %s' % '\n '.join(runlines)) 235*4882a593Smuzhiyun return -1 236*4882a593Smuzhiyun except bb.process.ExecutionError as e: 237*4882a593Smuzhiyun logger.error('Preparation failed:\n%s' % e.stdout) 238*4882a593Smuzhiyun return -1 239*4882a593Smuzhiyun return 0 240*4882a593Smuzhiyun 241*4882a593Smuzhiyundef sdk_install(args, config, basepath, workspace): 242*4882a593Smuzhiyun """Entry point for the devtool sdk-install command""" 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun import oe.recipeutils 245*4882a593Smuzhiyun import bb.process 246*4882a593Smuzhiyun 247*4882a593Smuzhiyun for recipe in args.recipename: 248*4882a593Smuzhiyun if recipe in workspace: 249*4882a593Smuzhiyun raise DevtoolError('recipe %s is a recipe in your workspace' % recipe) 250*4882a593Smuzhiyun 251*4882a593Smuzhiyun tasks = ['do_populate_sysroot', 'do_packagedata'] 252*4882a593Smuzhiyun stampprefixes = {} 253*4882a593Smuzhiyun def checkstamp(recipe): 254*4882a593Smuzhiyun stampprefix = stampprefixes[recipe] 255*4882a593Smuzhiyun stamps = glob.glob(stampprefix + '*') 256*4882a593Smuzhiyun for stamp in stamps: 257*4882a593Smuzhiyun if '.sigdata.' not in stamp and stamp.startswith((stampprefix + '.', stampprefix + '_setscene.')): 258*4882a593Smuzhiyun return True 259*4882a593Smuzhiyun else: 260*4882a593Smuzhiyun return False 261*4882a593Smuzhiyun 262*4882a593Smuzhiyun install_recipes = [] 263*4882a593Smuzhiyun tinfoil = setup_tinfoil(config_only=False, basepath=basepath) 264*4882a593Smuzhiyun try: 265*4882a593Smuzhiyun for recipe in args.recipename: 266*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, recipe, True) 267*4882a593Smuzhiyun if not rd: 268*4882a593Smuzhiyun return 1 269*4882a593Smuzhiyun stampprefixes[recipe] = '%s.%s' % (rd.getVar('STAMP'), tasks[0]) 270*4882a593Smuzhiyun if checkstamp(recipe): 271*4882a593Smuzhiyun logger.info('%s is already installed' % recipe) 272*4882a593Smuzhiyun else: 273*4882a593Smuzhiyun install_recipes.append(recipe) 274*4882a593Smuzhiyun finally: 275*4882a593Smuzhiyun tinfoil.shutdown() 276*4882a593Smuzhiyun 277*4882a593Smuzhiyun if install_recipes: 278*4882a593Smuzhiyun logger.info('Installing %s...' % ', '.join(install_recipes)) 279*4882a593Smuzhiyun install_tasks = [] 280*4882a593Smuzhiyun for recipe in install_recipes: 281*4882a593Smuzhiyun for task in tasks: 282*4882a593Smuzhiyun if recipe.endswith('-native') and 'package' in task: 283*4882a593Smuzhiyun continue 284*4882a593Smuzhiyun install_tasks.append('%s:%s' % (recipe, task)) 285*4882a593Smuzhiyun options = '' 286*4882a593Smuzhiyun if not args.allow_build: 287*4882a593Smuzhiyun options += ' --setscene-only' 288*4882a593Smuzhiyun try: 289*4882a593Smuzhiyun exec_build_env_command(config.init_path, basepath, 'bitbake %s %s' % (options, ' '.join(install_tasks)), watch=True) 290*4882a593Smuzhiyun except bb.process.ExecutionError as e: 291*4882a593Smuzhiyun raise DevtoolError('Failed to install %s:\n%s' % (recipe, str(e))) 292*4882a593Smuzhiyun failed = False 293*4882a593Smuzhiyun for recipe in install_recipes: 294*4882a593Smuzhiyun if checkstamp(recipe): 295*4882a593Smuzhiyun logger.info('Successfully installed %s' % recipe) 296*4882a593Smuzhiyun else: 297*4882a593Smuzhiyun raise DevtoolError('Failed to install %s - unavailable' % recipe) 298*4882a593Smuzhiyun failed = True 299*4882a593Smuzhiyun if failed: 300*4882a593Smuzhiyun return 2 301*4882a593Smuzhiyun 302*4882a593Smuzhiyun try: 303*4882a593Smuzhiyun exec_build_env_command(config.init_path, basepath, 'bitbake build-sysroots', watch=True) 304*4882a593Smuzhiyun except bb.process.ExecutionError as e: 305*4882a593Smuzhiyun raise DevtoolError('Failed to bitbake build-sysroots:\n%s' % (str(e))) 306*4882a593Smuzhiyun 307*4882a593Smuzhiyun 308*4882a593Smuzhiyundef register_commands(subparsers, context): 309*4882a593Smuzhiyun """Register devtool subcommands from the sdk plugin""" 310*4882a593Smuzhiyun if context.fixed_setup: 311*4882a593Smuzhiyun parser_sdk = subparsers.add_parser('sdk-update', 312*4882a593Smuzhiyun help='Update SDK components', 313*4882a593Smuzhiyun description='Updates installed SDK components from a remote server', 314*4882a593Smuzhiyun group='sdk') 315*4882a593Smuzhiyun updateserver = context.config.get('SDK', 'updateserver', '') 316*4882a593Smuzhiyun if updateserver: 317*4882a593Smuzhiyun parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from (default %s)' % updateserver, nargs='?') 318*4882a593Smuzhiyun else: 319*4882a593Smuzhiyun parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from') 320*4882a593Smuzhiyun parser_sdk.add_argument('--skip-prepare', action="store_true", help='Skip re-preparing the build system after updating (for debugging only)') 321*4882a593Smuzhiyun parser_sdk.set_defaults(func=sdk_update) 322*4882a593Smuzhiyun 323*4882a593Smuzhiyun parser_sdk_install = subparsers.add_parser('sdk-install', 324*4882a593Smuzhiyun help='Install additional SDK components', 325*4882a593Smuzhiyun description='Installs additional recipe development files into the SDK. (You can use "devtool search" to find available recipes.)', 326*4882a593Smuzhiyun group='sdk') 327*4882a593Smuzhiyun parser_sdk_install.add_argument('recipename', help='Name of the recipe to install the development artifacts for', nargs='+') 328*4882a593Smuzhiyun parser_sdk_install.add_argument('-s', '--allow-build', help='Allow building requested item(s) from source', action='store_true') 329*4882a593Smuzhiyun parser_sdk_install.set_defaults(func=sdk_install) 330