blob: a085f78c439022f147cd9b433439bd0dbdd2a80b [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - upgrade command plugin
2#
3# Copyright (C) 2014-2015 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#
18"""Devtool upgrade plugin"""
19
20import os
21import sys
22import re
23import shutil
24import tempfile
25import logging
26import argparse
27import scriptutils
28import errno
29import bb
30import oe.recipeutils
31from devtool import standard
Patrick Williamsf1e5d692016-03-30 15:21:19 -050032from devtool import exec_build_env_command, setup_tinfoil, DevtoolError, parse_recipe, use_external_build
Patrick Williamsc124f4f2015-09-15 14:41:29 -050033
34logger = logging.getLogger('devtool')
35
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036def _run(cmd, cwd=''):
37 logger.debug("Running command %s> %s" % (cwd,cmd))
38 return bb.process.run('%s' % cmd, cwd=cwd)
39
40def _get_srctree(tmpdir):
41 srctree = tmpdir
42 dirs = os.listdir(tmpdir)
43 if len(dirs) == 1:
44 srctree = os.path.join(tmpdir, dirs[0])
45 return srctree
46
47def _copy_source_code(orig, dest):
48 for path in standard._ls_tree(orig):
49 dest_dir = os.path.join(dest, os.path.dirname(path))
50 bb.utils.mkdirhier(dest_dir)
51 dest_path = os.path.join(dest, path)
Patrick Williamsf1e5d692016-03-30 15:21:19 -050052 shutil.move(os.path.join(orig, path), dest_path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050053
54def _get_checksums(rf):
55 import re
56 checksums = {}
57 with open(rf) as f:
58 for line in f:
59 for cs in ['md5sum', 'sha256sum']:
60 m = re.match("^SRC_URI\[%s\].*=.*\"(.*)\"" % cs, line)
61 if m:
62 checksums[cs] = m.group(1)
63 return checksums
64
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065def _remove_patch_dirs(recipefolder):
66 for root, dirs, files in os.walk(recipefolder):
67 for d in dirs:
68 shutil.rmtree(os.path.join(root,d))
69
Patrick Williamsf1e5d692016-03-30 15:21:19 -050070def _recipe_contains(rd, var):
71 rf = rd.getVar('FILE', True)
72 varfiles = oe.recipeutils.get_var_files(rf, [var], rd)
73 for var, fn in varfiles.iteritems():
74 if fn and fn.startswith(os.path.dirname(rf) + os.sep):
75 return True
76 return False
Patrick Williamsc124f4f2015-09-15 14:41:29 -050077
78def _rename_recipe_dirs(oldpv, newpv, path):
79 for root, dirs, files in os.walk(path):
80 for olddir in dirs:
81 if olddir.find(oldpv) != -1:
82 newdir = olddir.replace(oldpv, newpv)
83 if olddir != newdir:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050084 shutil.move(os.path.join(path, olddir), os.path.join(path, newdir))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050085
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086def _rename_recipe_file(oldrecipe, bpn, oldpv, newpv, path):
87 oldrecipe = os.path.basename(oldrecipe)
88 if oldrecipe.endswith('_%s.bb' % oldpv):
89 newrecipe = '%s_%s.bb' % (bpn, newpv)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090 if oldrecipe != newrecipe:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050091 shutil.move(os.path.join(path, oldrecipe), os.path.join(path, newrecipe))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050092 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050093 newrecipe = oldrecipe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050094 return os.path.join(path, newrecipe)
95
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050096def _rename_recipe_files(oldrecipe, bpn, oldpv, newpv, path):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050097 _rename_recipe_dirs(oldpv, newpv, path)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050098 return _rename_recipe_file(oldrecipe, bpn, oldpv, newpv, path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500100def _write_append(rc, srctree, same_dir, no_same_dir, rev, workspace, d):
101 """Writes an append file"""
102 if not os.path.exists(rc):
103 raise DevtoolError("bbappend not created because %s does not exist" % rc)
104
105 appendpath = os.path.join(workspace, 'appends')
106 if not os.path.exists(appendpath):
107 bb.utils.mkdirhier(appendpath)
108
109 brf = os.path.basename(os.path.splitext(rc)[0]) # rc basename
110
111 srctree = os.path.abspath(srctree)
112 pn = d.getVar('PN',True)
113 af = os.path.join(appendpath, '%s.bbappend' % brf)
114 with open(af, 'w') as f:
115 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n\n')
116 f.write('inherit externalsrc\n')
117 f.write(('# NOTE: We use pn- overrides here to avoid affecting'
118 'multiple variants in the case where the recipe uses BBCLASSEXTEND\n'))
119 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500120 b_is_s = use_external_build(same_dir, no_same_dir, d)
121 if b_is_s:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500122 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
123 if rev:
124 f.write('\n# initial_rev: %s\n' % rev)
125 return af
126
127def _cleanup_on_error(rf, srctree):
128 rfp = os.path.split(rf)[0] # recipe folder
129 rfpp = os.path.split(rfp)[0] # recipes folder
130 if os.path.exists(rfp):
131 shutil.rmtree(b)
132 if not len(os.listdir(rfpp)):
133 os.rmdir(rfpp)
134 srctree = os.path.abspath(srctree)
135 if os.path.exists(srctree):
136 shutil.rmtree(srctree)
137
138def _upgrade_error(e, rf, srctree):
139 if rf:
140 cleanup_on_error(rf, srctree)
141 logger.error(e)
142 raise DevtoolError(e)
143
144def _get_uri(rd):
145 srcuris = rd.getVar('SRC_URI', True).split()
146 if not len(srcuris):
147 raise DevtoolError('SRC_URI not found on recipe')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500148 # Get first non-local entry in SRC_URI - usually by convention it's
149 # the first entry, but not always!
150 srcuri = None
151 for entry in srcuris:
152 if not entry.startswith('file://'):
153 srcuri = entry
154 break
155 if not srcuri:
156 raise DevtoolError('Unable to find non-local entry in SRC_URI')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157 srcrev = '${AUTOREV}'
158 if '://' in srcuri:
159 # Fetch a URL
160 rev_re = re.compile(';rev=([^;]+)')
161 res = rev_re.search(srcuri)
162 if res:
163 srcrev = res.group(1)
164 srcuri = rev_re.sub('', srcuri)
165 return srcuri, srcrev
166
167def _extract_new_source(newpv, srctree, no_patch, srcrev, branch, keep_temp, tinfoil, rd):
168 """Extract sources of a recipe with a new version"""
169
170 def __run(cmd):
171 """Simple wrapper which calls _run with srctree as cwd"""
172 return _run(cmd, srctree)
173
174 crd = rd.createCopy()
175
176 pv = crd.getVar('PV', True)
177 crd.setVar('PV', newpv)
178
179 tmpsrctree = None
180 uri, rev = _get_uri(crd)
181 if srcrev:
182 rev = srcrev
183 if uri.startswith('git://'):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500184 __run('git fetch')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500185 __run('git checkout %s' % rev)
186 __run('git tag -f devtool-base-new')
187 md5 = None
188 sha256 = None
189 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500190 __run('git checkout devtool-base -b devtool-%s' % newpv)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500191
192 tmpdir = tempfile.mkdtemp(prefix='devtool')
193 try:
194 md5, sha256 = scriptutils.fetch_uri(tinfoil.config_data, uri, tmpdir, rev)
195 except bb.fetch2.FetchError as e:
196 raise DevtoolError(e)
197
198 tmpsrctree = _get_srctree(tmpdir)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500199 srctree = os.path.abspath(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500200
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500201 # Delete all sources so we ensure no stray files are left over
202 for item in os.listdir(srctree):
203 if item in ['.git', 'oe-local-files']:
204 continue
205 itempath = os.path.join(srctree, item)
206 if os.path.isdir(itempath):
207 shutil.rmtree(itempath)
208 else:
209 os.remove(itempath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500211 # Copy in new ones
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500212 _copy_source_code(tmpsrctree, srctree)
213
214 (stdout,_) = __run('git ls-files --modified --others --exclude-standard')
215 for f in stdout.splitlines():
216 __run('git add "%s"' % f)
217
218 __run('git commit -q -m "Commit of upstream changes at version %s" --allow-empty' % newpv)
219 __run('git tag -f devtool-base-%s' % newpv)
220
221 (stdout, _) = __run('git rev-parse HEAD')
222 rev = stdout.rstrip()
223
224 if no_patch:
225 patches = oe.recipeutils.get_recipe_patches(crd)
226 if len(patches):
227 logger.warn('By user choice, the following patches will NOT be applied')
228 for patch in patches:
229 logger.warn("%s" % os.path.basename(patch))
230 else:
231 try:
232 __run('git checkout devtool-patched -b %s' % branch)
233 __run('git rebase %s' % rev)
234 if uri.startswith('git://'):
235 suffix = 'new'
236 else:
237 suffix = newpv
238 __run('git tag -f devtool-patched-%s' % suffix)
239 except bb.process.ExecutionError as e:
240 logger.warn('Command \'%s\' failed:\n%s' % (e.command, e.stdout))
241
242 if tmpsrctree:
243 if keep_temp:
244 logger.info('Preserving temporary directory %s' % tmpsrctree)
245 else:
246 shutil.rmtree(tmpsrctree)
247
248 return (rev, md5, sha256)
249
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500250def _create_new_recipe(newpv, md5, sha256, srcrev, srcbranch, workspace, tinfoil, rd):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500251 """Creates the new recipe under workspace"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500252
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500253 bpn = rd.getVar('BPN', True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 path = os.path.join(workspace, 'recipes', bpn)
255 bb.utils.mkdirhier(path)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500256 oe.recipeutils.copy_recipe_files(rd, path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500257
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500258 oldpv = rd.getVar('PV', True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259 if not newpv:
260 newpv = oldpv
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500261 origpath = rd.getVar('FILE', True)
262 fullpath = _rename_recipe_files(origpath, bpn, oldpv, newpv, path)
263 logger.debug('Upgraded %s => %s' % (origpath, fullpath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500264
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500265 newvalues = {}
266 if _recipe_contains(rd, 'PV') and newpv != oldpv:
267 newvalues['PV'] = newpv
268
269 if srcrev:
270 newvalues['SRCREV'] = srcrev
271
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500272 if srcbranch:
273 src_uri = oe.recipeutils.split_var_value(rd.getVar('SRC_URI', False) or '')
274 changed = False
275 replacing = True
276 new_src_uri = []
277 for entry in src_uri:
278 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(entry)
279 if replacing and scheme in ['git', 'gitsm']:
280 branch = params.get('branch', 'master')
281 if rd.expand(branch) != srcbranch:
282 # Handle case where branch is set through a variable
283 res = re.match(r'\$\{([^}@]+)\}', branch)
284 if res:
285 newvalues[res.group(1)] = srcbranch
286 # We know we won't change SRC_URI now, so break out
287 break
288 else:
289 params['branch'] = srcbranch
290 entry = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
291 changed = True
292 replacing = False
293 new_src_uri.append(entry)
294 if changed:
295 newvalues['SRC_URI'] = ' '.join(new_src_uri)
296
297 newvalues['PR'] = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500298
299 if md5 and sha256:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500300 newvalues['SRC_URI[md5sum]'] = md5
301 newvalues['SRC_URI[sha256sum]'] = sha256
302
303 rd = oe.recipeutils.parse_recipe(fullpath, None, tinfoil.config_data)
304 oe.recipeutils.patch_recipe(rd, fullpath, newvalues)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500305
306 return fullpath
307
308def upgrade(args, config, basepath, workspace):
309 """Entry point for the devtool 'upgrade' subcommand"""
310
311 if args.recipename in workspace:
312 raise DevtoolError("recipe %s is already in your workspace" % args.recipename)
313 if not args.version and not args.srcrev:
314 raise DevtoolError("You must provide a version using the --version/-V option, or for recipes that fetch from an SCM such as git, the --srcrev/-S option")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500315 if args.srcbranch and not args.srcrev:
316 raise DevtoolError("If you specify --srcbranch/-B then you must use --srcrev/-S to specify the revision" % args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500317
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500318 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 rd = parse_recipe(config, tinfoil, args.recipename, True)
320 if not rd:
321 return 1
322
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500323 pn = rd.getVar('PN', True)
324 if pn != args.recipename:
325 logger.info('Mapping %s to %s' % (args.recipename, pn))
326 if pn in workspace:
327 raise DevtoolError("recipe %s is already in your workspace" % pn)
328
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500329 if args.srctree:
330 srctree = os.path.abspath(args.srctree)
331 else:
332 srctree = standard.get_default_srctree(config, pn)
333
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500334 standard._check_compatible_recipe(pn, rd)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500335 old_srcrev = rd.getVar('SRCREV', True)
336 if old_srcrev == 'INVALID':
337 old_srcrev = None
338 if old_srcrev and not args.srcrev:
339 raise DevtoolError("Recipe specifies a SRCREV value; you must specify a new one when upgrading")
340 if rd.getVar('PV', True) == args.version and old_srcrev == args.srcrev:
341 raise DevtoolError("Current and upgrade versions are the same version")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500342
343 rf = None
344 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500345 rev1 = standard._extract_source(srctree, False, 'devtool-orig', False, rd)
346 rev2, md5, sha256 = _extract_new_source(args.version, srctree, args.no_patch,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500347 args.srcrev, args.branch, args.keep_temp,
348 tinfoil, rd)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500349 rf = _create_new_recipe(args.version, md5, sha256, args.srcrev, args.srcbranch, config.workspace_path, tinfoil, rd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500350 except bb.process.CmdError as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500351 _upgrade_error(e, rf, srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500352 except DevtoolError as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500353 _upgrade_error(e, rf, srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500354 standard._add_md5(config, pn, os.path.dirname(rf))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500355
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500356 af = _write_append(rf, srctree, args.same_dir, args.no_same_dir, rev2,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500357 config.workspace_path, rd)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500358 standard._add_md5(config, pn, af)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500359 logger.info('Upgraded source extracted to %s' % srctree)
360 logger.info('New recipe is %s' % rf)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500361 return 0
362
363def register_commands(subparsers, context):
364 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500365
366 defsrctree = standard.get_default_srctree(context.config)
367
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500368 parser_upgrade = subparsers.add_parser('upgrade', help='Upgrade an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500369 description='Upgrades an existing recipe to a new upstream version. Puts the upgraded recipe file into the workspace along with any associated files, and extracts the source tree to a specified location (in case patches need rebasing or adding to as a result of the upgrade).',
370 group='starting')
371 parser_upgrade.add_argument('recipename', help='Name of recipe to upgrade (just name - no version, path or extension)')
372 parser_upgrade.add_argument('srctree', nargs='?', help='Path to where to extract the source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500373 parser_upgrade.add_argument('--version', '-V', help='Version to upgrade to (PV)')
374 parser_upgrade.add_argument('--srcrev', '-S', help='Source revision to upgrade to (if fetching from an SCM such as git)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500375 parser_upgrade.add_argument('--srcbranch', '-B', help='Branch in source repository containing the revision to use (if fetching from an SCM such as git)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500376 parser_upgrade.add_argument('--branch', '-b', default="devtool", help='Name for new development branch to checkout (default "%(default)s")')
377 parser_upgrade.add_argument('--no-patch', action="store_true", help='Do not apply patches from the recipe to the new source code')
378 group = parser_upgrade.add_mutually_exclusive_group()
379 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
380 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
381 parser_upgrade.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
382 parser_upgrade.set_defaults(func=upgrade)