1*4882a593Smuzhiyun# Development tool - import command plugin 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (C) 2014-2017 Intel Corporation 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun"""Devtool import plugin""" 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunimport os 10*4882a593Smuzhiyunimport tarfile 11*4882a593Smuzhiyunimport logging 12*4882a593Smuzhiyunimport collections 13*4882a593Smuzhiyunimport json 14*4882a593Smuzhiyunimport fnmatch 15*4882a593Smuzhiyun 16*4882a593Smuzhiyunfrom devtool import standard, setup_tinfoil, replace_from_file, DevtoolError 17*4882a593Smuzhiyunfrom devtool import export 18*4882a593Smuzhiyun 19*4882a593Smuzhiyunlogger = logging.getLogger('devtool') 20*4882a593Smuzhiyun 21*4882a593Smuzhiyundef devimport(args, config, basepath, workspace): 22*4882a593Smuzhiyun """Entry point for the devtool 'import' subcommand""" 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun def get_pn(name): 25*4882a593Smuzhiyun """ Returns the filename of a workspace recipe/append""" 26*4882a593Smuzhiyun metadata = name.split('/')[-1] 27*4882a593Smuzhiyun fn, _ = os.path.splitext(metadata) 28*4882a593Smuzhiyun return fn 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun if not os.path.exists(args.file): 31*4882a593Smuzhiyun raise DevtoolError('Tar archive %s does not exist. Export your workspace using "devtool export"' % args.file) 32*4882a593Smuzhiyun 33*4882a593Smuzhiyun with tarfile.open(args.file) as tar: 34*4882a593Smuzhiyun # Get exported metadata 35*4882a593Smuzhiyun export_workspace_path = export_workspace = None 36*4882a593Smuzhiyun try: 37*4882a593Smuzhiyun metadata = tar.getmember(export.metadata) 38*4882a593Smuzhiyun except KeyError as ke: 39*4882a593Smuzhiyun raise DevtoolError('The export metadata file created by "devtool export" was not found. "devtool import" can only be used to import tar archives created by "devtool export".') 40*4882a593Smuzhiyun 41*4882a593Smuzhiyun tar.extract(metadata) 42*4882a593Smuzhiyun with open(metadata.name) as fdm: 43*4882a593Smuzhiyun export_workspace_path, export_workspace = json.load(fdm) 44*4882a593Smuzhiyun os.unlink(metadata.name) 45*4882a593Smuzhiyun 46*4882a593Smuzhiyun members = tar.getmembers() 47*4882a593Smuzhiyun 48*4882a593Smuzhiyun # Get appends and recipes from the exported archive, these 49*4882a593Smuzhiyun # will be needed to find out those appends without corresponding 50*4882a593Smuzhiyun # recipe pair 51*4882a593Smuzhiyun append_fns, recipe_fns = set(), set() 52*4882a593Smuzhiyun for member in members: 53*4882a593Smuzhiyun if member.name.startswith('appends'): 54*4882a593Smuzhiyun append_fns.add(get_pn(member.name)) 55*4882a593Smuzhiyun elif member.name.startswith('recipes'): 56*4882a593Smuzhiyun recipe_fns.add(get_pn(member.name)) 57*4882a593Smuzhiyun 58*4882a593Smuzhiyun # Setup tinfoil, get required data and shutdown 59*4882a593Smuzhiyun tinfoil = setup_tinfoil(config_only=False, basepath=basepath) 60*4882a593Smuzhiyun try: 61*4882a593Smuzhiyun current_fns = [os.path.basename(recipe[0]) for recipe in tinfoil.cooker.recipecaches[''].pkg_fn.items()] 62*4882a593Smuzhiyun finally: 63*4882a593Smuzhiyun tinfoil.shutdown() 64*4882a593Smuzhiyun 65*4882a593Smuzhiyun # Find those appends that do not have recipes in current metadata 66*4882a593Smuzhiyun non_importables = [] 67*4882a593Smuzhiyun for fn in append_fns - recipe_fns: 68*4882a593Smuzhiyun # Check on current metadata (covering those layers indicated in bblayers.conf) 69*4882a593Smuzhiyun for current_fn in current_fns: 70*4882a593Smuzhiyun if fnmatch.fnmatch(current_fn, '*' + fn.replace('%', '') + '*'): 71*4882a593Smuzhiyun break 72*4882a593Smuzhiyun else: 73*4882a593Smuzhiyun non_importables.append(fn) 74*4882a593Smuzhiyun logger.warning('No recipe to append %s.bbapppend, skipping' % fn) 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun # Extract 77*4882a593Smuzhiyun imported = [] 78*4882a593Smuzhiyun for member in members: 79*4882a593Smuzhiyun if member.name == export.metadata: 80*4882a593Smuzhiyun continue 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun for nonimp in non_importables: 83*4882a593Smuzhiyun pn = nonimp.split('_')[0] 84*4882a593Smuzhiyun # do not extract data from non-importable recipes or metadata 85*4882a593Smuzhiyun if member.name.startswith('appends/%s' % nonimp) or \ 86*4882a593Smuzhiyun member.name.startswith('recipes/%s' % nonimp) or \ 87*4882a593Smuzhiyun member.name.startswith('sources/%s' % pn): 88*4882a593Smuzhiyun break 89*4882a593Smuzhiyun else: 90*4882a593Smuzhiyun path = os.path.join(config.workspace_path, member.name) 91*4882a593Smuzhiyun if os.path.exists(path): 92*4882a593Smuzhiyun # by default, no file overwrite is done unless -o is given by the user 93*4882a593Smuzhiyun if args.overwrite: 94*4882a593Smuzhiyun try: 95*4882a593Smuzhiyun tar.extract(member, path=config.workspace_path) 96*4882a593Smuzhiyun except PermissionError as pe: 97*4882a593Smuzhiyun logger.warning(pe) 98*4882a593Smuzhiyun else: 99*4882a593Smuzhiyun logger.warning('File already present. Use --overwrite/-o to overwrite it: %s' % member.name) 100*4882a593Smuzhiyun continue 101*4882a593Smuzhiyun else: 102*4882a593Smuzhiyun tar.extract(member, path=config.workspace_path) 103*4882a593Smuzhiyun 104*4882a593Smuzhiyun # Update EXTERNALSRC and the devtool md5 file 105*4882a593Smuzhiyun if member.name.startswith('appends'): 106*4882a593Smuzhiyun if export_workspace_path: 107*4882a593Smuzhiyun # appends created by 'devtool modify' just need to update the workspace 108*4882a593Smuzhiyun replace_from_file(path, export_workspace_path, config.workspace_path) 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun # appends created by 'devtool add' need replacement of exported source tree 111*4882a593Smuzhiyun pn = get_pn(member.name).split('_')[0] 112*4882a593Smuzhiyun exported_srctree = export_workspace[pn]['srctree'] 113*4882a593Smuzhiyun if exported_srctree: 114*4882a593Smuzhiyun replace_from_file(path, exported_srctree, os.path.join(config.workspace_path, 'sources', pn)) 115*4882a593Smuzhiyun 116*4882a593Smuzhiyun standard._add_md5(config, pn, path) 117*4882a593Smuzhiyun imported.append(pn) 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun if imported: 120*4882a593Smuzhiyun logger.info('Imported recipes into workspace %s: %s' % (config.workspace_path, ', '.join(imported))) 121*4882a593Smuzhiyun else: 122*4882a593Smuzhiyun logger.warning('No recipes imported into the workspace') 123*4882a593Smuzhiyun 124*4882a593Smuzhiyun return 0 125*4882a593Smuzhiyun 126*4882a593Smuzhiyundef register_commands(subparsers, context): 127*4882a593Smuzhiyun """Register devtool import subcommands""" 128*4882a593Smuzhiyun parser = subparsers.add_parser('import', 129*4882a593Smuzhiyun help='Import exported tar archive into workspace', 130*4882a593Smuzhiyun description='Import tar archive previously created by "devtool export" into workspace', 131*4882a593Smuzhiyun group='advanced') 132*4882a593Smuzhiyun parser.add_argument('file', metavar='FILE', help='Name of the tar archive to import') 133*4882a593Smuzhiyun parser.add_argument('--overwrite', '-o', action="store_true", help='Overwrite files when extracting') 134*4882a593Smuzhiyun parser.set_defaults(func=devimport) 135