blob: 46fd12bdb23ca4f401789319460351cfdee5fcd5 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - sdk-update command plugin
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002#
3# Copyright (C) 2015-2016 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.
Patrick Williamsc124f4f2015-09-15 14:41:29 -050017
18import os
19import subprocess
20import logging
21import glob
22import shutil
23import errno
24import sys
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050025import tempfile
26import re
27from devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028
29logger = logging.getLogger('devtool')
30
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031def parse_locked_sigs(sigfile_path):
32 """Return <pn:task>:<hash> dictionary"""
33 sig_dict = {}
34 with open(sigfile_path) as f:
35 lines = f.readlines()
36 for line in lines:
37 if ':' in line:
38 taskkey, _, hashval = line.rpartition(':')
39 sig_dict[taskkey.strip()] = hashval.split()[0]
40 return sig_dict
41
42def generate_update_dict(sigfile_new, sigfile_old):
43 """Return a dict containing <pn:task>:<hash> which indicates what need to be updated"""
44 update_dict = {}
45 sigdict_new = parse_locked_sigs(sigfile_new)
46 sigdict_old = parse_locked_sigs(sigfile_old)
47 for k in sigdict_new:
48 if k not in sigdict_old:
49 update_dict[k] = sigdict_new[k]
50 continue
51 if sigdict_new[k] != sigdict_old[k]:
52 update_dict[k] = sigdict_new[k]
53 continue
54 return update_dict
55
Patrick Williamsf1e5d692016-03-30 15:21:19 -050056def get_sstate_objects(update_dict, sstate_dir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057 """Return a list containing sstate objects which are to be installed"""
58 sstate_objects = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -050059 for k in update_dict:
60 files = set()
61 hashval = update_dict[k]
62 p = sstate_dir + '/' + hashval[:2] + '/*' + hashval + '*.tgz'
63 files |= set(glob.glob(p))
64 p = sstate_dir + '/*/' + hashval[:2] + '/*' + hashval + '*.tgz'
65 files |= set(glob.glob(p))
66 files = list(files)
67 if len(files) == 1:
68 sstate_objects.extend(files)
69 elif len(files) > 1:
70 logger.error("More than one matching sstate object found for %s" % hashval)
71
72 return sstate_objects
73
74def mkdir(d):
75 try:
76 os.makedirs(d)
77 except OSError as e:
78 if e.errno != errno.EEXIST:
79 raise e
80
81def install_sstate_objects(sstate_objects, src_sdk, dest_sdk):
82 """Install sstate objects into destination SDK"""
83 sstate_dir = os.path.join(dest_sdk, 'sstate-cache')
84 if not os.path.exists(sstate_dir):
85 logger.error("Missing sstate-cache directory in %s, it might not be an extensible SDK." % dest_sdk)
86 raise
87 for sb in sstate_objects:
88 dst = sb.replace(src_sdk, dest_sdk)
89 destdir = os.path.dirname(dst)
90 mkdir(destdir)
91 logger.debug("Copying %s to %s" % (sb, dst))
92 shutil.copy(sb, dst)
93
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050094def check_manifest(fn, basepath):
95 import bb.utils
96 changedfiles = []
97 with open(fn, 'r') as f:
98 for line in f:
99 splitline = line.split()
100 if len(splitline) > 1:
101 chksum = splitline[0]
102 fpath = splitline[1]
103 curr_chksum = bb.utils.sha256_file(os.path.join(basepath, fpath))
104 if chksum != curr_chksum:
105 logger.debug('File %s changed: old csum = %s, new = %s' % (os.path.join(basepath, fpath), curr_chksum, chksum))
106 changedfiles.append(fpath)
107 return changedfiles
108
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500109def sdk_update(args, config, basepath, workspace):
110 # Fetch locked-sigs.inc file from remote/local destination
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500111 updateserver = args.updateserver
112 if not updateserver:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500113 updateserver = config.get('SDK', 'updateserver', '')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500114 logger.debug("updateserver: %s" % updateserver)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500115
116 # Make sure we are using sdk-update from within SDK
117 logger.debug("basepath = %s" % basepath)
118 old_locked_sig_file_path = os.path.join(basepath, 'conf/locked-sigs.inc')
119 if not os.path.exists(old_locked_sig_file_path):
120 logger.error("Not using devtool's sdk-update command from within an extensible SDK. Please specify correct basepath via --basepath option")
121 return -1
122 else:
123 logger.debug("Found conf/locked-sigs.inc in %s" % basepath)
124
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500125 if ':' in updateserver:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500126 is_remote = True
127 else:
128 is_remote = False
129
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500130 layers_dir = os.path.join(basepath, 'layers')
131 conf_dir = os.path.join(basepath, 'conf')
132
133 # Grab variable values
134 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
135 try:
136 stamps_dir = tinfoil.config_data.getVar('STAMPS_DIR', True)
137 sstate_mirrors = tinfoil.config_data.getVar('SSTATE_MIRRORS', True)
138 site_conf_version = tinfoil.config_data.getVar('SITE_CONF_VERSION', True)
139 finally:
140 tinfoil.shutdown()
141
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500142 if not is_remote:
143 # devtool sdk-update /local/path/to/latest/sdk
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500144 new_locked_sig_file_path = os.path.join(updateserver, 'conf/locked-sigs.inc')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500145 if not os.path.exists(new_locked_sig_file_path):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500146 logger.error("%s doesn't exist or is not an extensible SDK" % updateserver)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500147 return -1
148 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500149 logger.debug("Found conf/locked-sigs.inc in %s" % updateserver)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500150 update_dict = generate_update_dict(new_locked_sig_file_path, old_locked_sig_file_path)
151 logger.debug("update_dict = %s" % update_dict)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500152 newsdk_path = updateserver
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500153 sstate_dir = os.path.join(newsdk_path, 'sstate-cache')
154 if not os.path.exists(sstate_dir):
155 logger.error("sstate-cache directory not found under %s" % newsdk_path)
156 return 1
157 sstate_objects = get_sstate_objects(update_dict, sstate_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500158 logger.debug("sstate_objects = %s" % sstate_objects)
159 if len(sstate_objects) == 0:
160 logger.info("No need to update.")
161 return 0
162 logger.info("Installing sstate objects into %s", basepath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500163 install_sstate_objects(sstate_objects, updateserver.rstrip('/'), basepath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500164 logger.info("Updating configuration files")
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500165 new_conf_dir = os.path.join(updateserver, 'conf')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500166 shutil.rmtree(conf_dir)
167 shutil.copytree(new_conf_dir, conf_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500168 logger.info("Updating layers")
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500169 new_layers_dir = os.path.join(updateserver, 'layers')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500170 shutil.rmtree(layers_dir)
171 ret = subprocess.call("cp -a %s %s" % (new_layers_dir, layers_dir), shell=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500172 if ret != 0:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500173 logger.error("Copying %s to %s failed" % (new_layers_dir, layers_dir))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500174 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500175 else:
176 # devtool sdk-update http://myhost/sdk
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500177 tmpsdk_dir = tempfile.mkdtemp()
178 try:
179 os.makedirs(os.path.join(tmpsdk_dir, 'conf'))
180 new_locked_sig_file_path = os.path.join(tmpsdk_dir, 'conf', 'locked-sigs.inc')
181 # Fetch manifest from server
182 tmpmanifest = os.path.join(tmpsdk_dir, 'conf', 'sdk-conf-manifest')
183 ret = subprocess.call("wget -q -O %s %s/conf/sdk-conf-manifest" % (tmpmanifest, updateserver), shell=True)
184 changedfiles = check_manifest(tmpmanifest, basepath)
185 if not changedfiles:
186 logger.info("Already up-to-date")
187 return 0
188 # Update metadata
189 logger.debug("Updating metadata via git ...")
190 #Check for the status before doing a fetch and reset
191 if os.path.exists(os.path.join(basepath, 'layers/.git')):
192 out = subprocess.check_output("git status --porcelain", shell=True, cwd=layers_dir)
193 if not out:
194 ret = subprocess.call("git fetch --all; git reset --hard", shell=True, cwd=layers_dir)
195 else:
196 logger.error("Failed to update metadata as there have been changes made to it. Aborting.");
197 logger.error("Changed files:\n%s" % out);
198 return -1
199 else:
200 ret = -1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500201 if ret != 0:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500202 ret = subprocess.call("git clone %s/layers/.git" % updateserver, shell=True, cwd=tmpsdk_dir)
203 if ret != 0:
204 logger.error("Updating metadata via git failed")
205 return ret
206 logger.debug("Updating conf files ...")
207 for changedfile in changedfiles:
208 ret = subprocess.call("wget -q -O %s %s/%s" % (changedfile, updateserver, changedfile), shell=True, cwd=tmpsdk_dir)
209 if ret != 0:
210 logger.error("Updating %s failed" % changedfile)
211 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500212
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500213 # Check if UNINATIVE_CHECKSUM changed
214 uninative = False
215 if 'conf/local.conf' in changedfiles:
216 def read_uninative_checksums(fn):
217 chksumitems = []
218 with open(fn, 'r') as f:
219 for line in f:
220 if line.startswith('UNINATIVE_CHECKSUM'):
221 splitline = re.split(r'[\[\]"\']', line)
222 if len(splitline) > 3:
223 chksumitems.append((splitline[1], splitline[3]))
224 return chksumitems
225
226 oldsums = read_uninative_checksums(os.path.join(basepath, 'conf/local.conf'))
227 newsums = read_uninative_checksums(os.path.join(tmpsdk_dir, 'conf/local.conf'))
228 if oldsums != newsums:
229 uninative = True
230 for buildarch, chksum in newsums:
231 uninative_file = os.path.join('downloads', 'uninative', chksum, '%s-nativesdk-libc.tar.bz2' % buildarch)
232 mkdir(os.path.join(tmpsdk_dir, os.path.dirname(uninative_file)))
233 ret = subprocess.call("wget -q -O %s %s/%s" % (uninative_file, updateserver, uninative_file), shell=True, cwd=tmpsdk_dir)
234
235 # Ok, all is well at this point - move everything over
236 tmplayers_dir = os.path.join(tmpsdk_dir, 'layers')
237 if os.path.exists(tmplayers_dir):
238 shutil.rmtree(layers_dir)
239 shutil.move(tmplayers_dir, layers_dir)
240 for changedfile in changedfiles:
241 destfile = os.path.join(basepath, changedfile)
242 os.remove(destfile)
243 shutil.move(os.path.join(tmpsdk_dir, changedfile), destfile)
244 os.remove(os.path.join(conf_dir, 'sdk-conf-manifest'))
245 shutil.move(tmpmanifest, conf_dir)
246 if uninative:
247 shutil.rmtree(os.path.join(basepath, 'downloads', 'uninative'))
248 shutil.move(os.path.join(tmpsdk_dir, 'downloads', 'uninative'), os.path.join(basepath, 'downloads'))
249
250 if not sstate_mirrors:
251 with open(os.path.join(conf_dir, 'site.conf'), 'a') as f:
252 f.write('SCONF_VERSION = "%s"\n' % site_conf_version)
253 f.write('SSTATE_MIRRORS_append = " file://.* %s/sstate-cache/PATH \\n "\n' % updateserver)
254 finally:
255 shutil.rmtree(tmpsdk_dir)
256
257 if not args.skip_prepare:
258 # Find all potentially updateable tasks
259 sdk_update_targets = []
260 tasks = ['do_populate_sysroot', 'do_packagedata']
261 for root, _, files in os.walk(stamps_dir):
262 for fn in files:
263 if not '.sigdata.' in fn:
264 for task in tasks:
265 if '.%s.' % task in fn or '.%s_setscene.' % task in fn:
266 sdk_update_targets.append('%s:%s' % (os.path.basename(root), task))
267 # Run bitbake command for the whole SDK
268 logger.info("Preparing build system... (This may take some time.)")
269 try:
270 exec_build_env_command(config.init_path, basepath, 'bitbake --setscene-only %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT)
271 output, _ = exec_build_env_command(config.init_path, basepath, 'bitbake -n %s' % ' '.join(sdk_update_targets), stderr=subprocess.STDOUT)
272 runlines = []
273 for line in output.splitlines():
274 if 'Running task ' in line:
275 runlines.append(line)
276 if runlines:
277 logger.error('Unexecuted tasks found in preparation log:\n %s' % '\n '.join(runlines))
278 return -1
279 except bb.process.ExecutionError as e:
280 logger.error('Preparation failed:\n%s' % e.stdout)
281 return -1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500282 return 0
283
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500284def sdk_install(args, config, basepath, workspace):
285 """Entry point for the devtool sdk-install command"""
286
287 import oe.recipeutils
288 import bb.process
289
290 for recipe in args.recipename:
291 if recipe in workspace:
292 raise DevtoolError('recipe %s is a recipe in your workspace' % recipe)
293
294 tasks = ['do_populate_sysroot', 'do_packagedata']
295 stampprefixes = {}
296 def checkstamp(recipe):
297 stampprefix = stampprefixes[recipe]
298 stamps = glob.glob(stampprefix + '*')
299 for stamp in stamps:
300 if '.sigdata.' not in stamp and stamp.startswith((stampprefix + '.', stampprefix + '_setscene.')):
301 return True
302 else:
303 return False
304
305 install_recipes = []
306 tinfoil = setup_tinfoil(config_only=False, basepath=basepath)
307 try:
308 for recipe in args.recipename:
309 rd = parse_recipe(config, tinfoil, recipe, True)
310 if not rd:
311 return 1
312 stampprefixes[recipe] = '%s.%s' % (rd.getVar('STAMP', True), tasks[0])
313 if checkstamp(recipe):
314 logger.info('%s is already installed' % recipe)
315 else:
316 install_recipes.append(recipe)
317 finally:
318 tinfoil.shutdown()
319
320 if install_recipes:
321 logger.info('Installing %s...' % ', '.join(install_recipes))
322 install_tasks = []
323 for recipe in install_recipes:
324 for task in tasks:
325 if recipe.endswith('-native') and 'package' in task:
326 continue
327 install_tasks.append('%s:%s' % (recipe, task))
328 options = ''
329 if not args.allow_build:
330 options += ' --setscene-only'
331 try:
332 exec_build_env_command(config.init_path, basepath, 'bitbake %s %s' % (options, ' '.join(install_tasks)), watch=True)
333 except bb.process.ExecutionError as e:
334 raise DevtoolError('Failed to install %s:\n%s' % (recipe, str(e)))
335 failed = False
336 for recipe in install_recipes:
337 if checkstamp(recipe):
338 logger.info('Successfully installed %s' % recipe)
339 else:
340 raise DevtoolError('Failed to install %s - unavailable' % recipe)
341 failed = True
342 if failed:
343 return 2
344
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500345def register_commands(subparsers, context):
346 """Register devtool subcommands from the sdk plugin"""
347 if context.fixed_setup:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500348 parser_sdk = subparsers.add_parser('sdk-update',
349 help='Update SDK components',
350 description='Updates installed SDK components from a remote server',
351 group='sdk')
352 updateserver = context.config.get('SDK', 'updateserver', '')
353 if updateserver:
354 parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from (default %s)' % updateserver, nargs='?')
355 else:
356 parser_sdk.add_argument('updateserver', help='The update server to fetch latest SDK components from')
357 parser_sdk.add_argument('--skip-prepare', action="store_true", help='Skip re-preparing the build system after updating (for debugging only)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500358 parser_sdk.set_defaults(func=sdk_update)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500359
360 parser_sdk_install = subparsers.add_parser('sdk-install',
361 help='Install additional SDK components',
362 description='Installs additional recipe development files into the SDK. (You can use "devtool search" to find available recipes.)',
363 group='sdk')
364 parser_sdk_install.add_argument('recipename', help='Name of the recipe to install the development artifacts for', nargs='+')
365 parser_sdk_install.add_argument('-s', '--allow-build', help='Allow building requested item(s) from source', action='store_true')
366 parser_sdk_install.set_defaults(func=sdk_install)