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