blob: a3710285bbb0f3c6b5b4672d40aa5b8be4f0de50 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Recipe creation tool - create command plugin
2#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05003# Copyright (C) 2014-2017 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
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
18import sys
19import os
20import argparse
21import glob
22import fnmatch
23import re
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050024import json
Patrick Williamsc124f4f2015-09-15 14:41:29 -050025import logging
26import scriptutils
Patrick Williamsc0f7c042017-02-23 20:41:17 -060027from urllib.parse import urlparse, urldefrag, urlsplit
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050028import hashlib
Brad Bishop6e60e8b2018-02-01 10:27:11 -050029import bb.fetch2
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030logger = logging.getLogger('recipetool')
31
32tinfoil = None
33plugins = None
34
Brad Bishop6e60e8b2018-02-01 10:27:11 -050035def log_error_cond(message, debugonly):
36 if debugonly:
37 logger.debug(message)
38 else:
39 logger.error(message)
40
41def log_info_cond(message, debugonly):
42 if debugonly:
43 logger.debug(message)
44 else:
45 logger.info(message)
46
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047def plugin_init(pluginlist):
48 # Take a reference to the list so we can use it later
49 global plugins
50 plugins = pluginlist
51
52def tinfoil_init(instance):
53 global tinfoil
54 tinfoil = instance
55
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050056class RecipeHandler(object):
57 recipelibmap = {}
58 recipeheadermap = {}
59 recipecmakefilemap = {}
60 recipebinmap = {}
61
Brad Bishop6e60e8b2018-02-01 10:27:11 -050062 def __init__(self):
63 self._devtool = False
64
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065 @staticmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050066 def load_libmap(d):
67 '''Load library->recipe mapping'''
68 import oe.package
69
70 if RecipeHandler.recipelibmap:
71 return
72 # First build up library->package mapping
73 shlib_providers = oe.package.read_shlib_providers(d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050074 libdir = d.getVar('libdir')
75 base_libdir = d.getVar('base_libdir')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050076 libpaths = list(set([base_libdir, libdir]))
77 libname_re = re.compile('^lib(.+)\.so.*$')
78 pkglibmap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -060079 for lib, item in shlib_providers.items():
80 for path, pkg in item.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050081 if path in libpaths:
82 res = libname_re.match(lib)
83 if res:
84 libname = res.group(1)
85 if not libname in pkglibmap:
86 pkglibmap[libname] = pkg[0]
87 else:
88 logger.debug('unable to extract library name from %s' % lib)
89
90 # Now turn it into a library->recipe mapping
Brad Bishop6e60e8b2018-02-01 10:27:11 -050091 pkgdata_dir = d.getVar('PKGDATA_DIR')
Patrick Williamsc0f7c042017-02-23 20:41:17 -060092 for libname, pkg in pkglibmap.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050093 try:
94 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
95 for line in f:
96 if line.startswith('PN:'):
97 RecipeHandler.recipelibmap[libname] = line.split(':', 1)[-1].strip()
98 break
99 except IOError as ioe:
100 if ioe.errno == 2:
101 logger.warn('unable to find a pkgdata file for package %s' % pkg)
102 else:
103 raise
104
105 # Some overrides - these should be mapped to the virtual
106 RecipeHandler.recipelibmap['GL'] = 'virtual/libgl'
107 RecipeHandler.recipelibmap['EGL'] = 'virtual/egl'
108 RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2'
109
110 @staticmethod
111 def load_devel_filemap(d):
112 '''Build up development file->recipe mapping'''
113 if RecipeHandler.recipeheadermap:
114 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500115 pkgdata_dir = d.getVar('PKGDATA_DIR')
116 includedir = d.getVar('includedir')
117 cmakedir = os.path.join(d.getVar('libdir'), 'cmake')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500118 for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')):
119 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
120 pn = None
121 headers = []
122 cmakefiles = []
123 for line in f:
124 if line.startswith('PN:'):
125 pn = line.split(':', 1)[-1].strip()
126 elif line.startswith('FILES_INFO:'):
127 val = line.split(':', 1)[1].strip()
128 dictval = json.loads(val)
129 for fullpth in sorted(dictval):
130 if fullpth.startswith(includedir) and fullpth.endswith('.h'):
131 headers.append(os.path.relpath(fullpth, includedir))
132 elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'):
133 cmakefiles.append(os.path.relpath(fullpth, cmakedir))
134 if pn and headers:
135 for header in headers:
136 RecipeHandler.recipeheadermap[header] = pn
137 if pn and cmakefiles:
138 for fn in cmakefiles:
139 RecipeHandler.recipecmakefilemap[fn] = pn
140
141 @staticmethod
142 def load_binmap(d):
143 '''Build up native binary->recipe mapping'''
144 if RecipeHandler.recipebinmap:
145 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500146 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
147 staging_bindir_native = d.getVar('STAGING_BINDIR_NATIVE')
148 build_arch = d.getVar('BUILD_ARCH')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500149 fileprefix = 'manifest-%s-' % build_arch
150 for fn in glob.glob(os.path.join(sstate_manifests, '%s*-native.populate_sysroot' % fileprefix)):
151 with open(fn, 'r') as f:
152 pn = os.path.basename(fn).rsplit('.', 1)[0][len(fileprefix):]
153 for line in f:
154 if line.startswith(staging_bindir_native):
155 prog = os.path.basename(line.rstrip())
156 RecipeHandler.recipebinmap[prog] = pn
157
158 @staticmethod
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500159 def checkfiles(path, speclist, recursive=False, excludedirs=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160 results = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500161 if recursive:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500162 for root, dirs, files in os.walk(path, topdown=True):
163 if excludedirs:
164 dirs[:] = [d for d in dirs if d not in excludedirs]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500165 for fn in files:
166 for spec in speclist:
167 if fnmatch.fnmatch(fn, spec):
168 results.append(os.path.join(root, fn))
169 else:
170 for spec in speclist:
171 results.extend(glob.glob(os.path.join(path, spec)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500172 return results
173
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500174 @staticmethod
175 def handle_depends(libdeps, pcdeps, deps, outlines, values, d):
176 if pcdeps:
177 recipemap = read_pkgconfig_provides(d)
178 if libdeps:
179 RecipeHandler.load_libmap(d)
180
181 ignorelibs = ['socket']
182 ignoredeps = ['gcc-runtime', 'glibc', 'uclibc', 'musl', 'tar-native', 'binutils-native', 'coreutils-native']
183
184 unmappedpc = []
185 pcdeps = list(set(pcdeps))
186 for pcdep in pcdeps:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600187 if isinstance(pcdep, str):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500188 recipe = recipemap.get(pcdep, None)
189 if recipe:
190 deps.append(recipe)
191 else:
192 if not pcdep.startswith('$'):
193 unmappedpc.append(pcdep)
194 else:
195 for item in pcdep:
196 recipe = recipemap.get(pcdep, None)
197 if recipe:
198 deps.append(recipe)
199 break
200 else:
201 unmappedpc.append('(%s)' % ' or '.join(pcdep))
202
203 unmappedlibs = []
204 for libdep in libdeps:
205 if isinstance(libdep, tuple):
206 lib, header = libdep
207 else:
208 lib = libdep
209 header = None
210
211 if lib in ignorelibs:
212 logger.debug('Ignoring library dependency %s' % lib)
213 continue
214
215 recipe = RecipeHandler.recipelibmap.get(lib, None)
216 if recipe:
217 deps.append(recipe)
218 elif recipe is None:
219 if header:
220 RecipeHandler.load_devel_filemap(d)
221 recipe = RecipeHandler.recipeheadermap.get(header, None)
222 if recipe:
223 deps.append(recipe)
224 elif recipe is None:
225 unmappedlibs.append(lib)
226 else:
227 unmappedlibs.append(lib)
228
229 deps = set(deps).difference(set(ignoredeps))
230
231 if unmappedpc:
232 outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmappedpc))
233 outlines.append('# (this is based on recipes that have previously been built and packaged)')
234
235 if unmappedlibs:
236 outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmappedlibs))))
237 outlines.append('# (this is based on recipes that have previously been built and packaged)')
238
239 if deps:
240 values['DEPENDS'] = ' '.join(deps)
241
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500242 @staticmethod
243 def genfunction(outlines, funcname, content, python=False, forcespace=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500244 if python:
245 prefix = 'python '
246 else:
247 prefix = ''
248 outlines.append('%s%s () {' % (prefix, funcname))
249 if python or forcespace:
250 indent = ' '
251 else:
252 indent = '\t'
253 addnoop = not python
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 for line in content:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500255 outlines.append('%s%s' % (indent, line))
256 if addnoop:
257 strippedline = line.lstrip()
258 if strippedline and not strippedline.startswith('#'):
259 addnoop = False
260 if addnoop:
261 # Without this there'll be a syntax error
262 outlines.append('%s:' % indent)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263 outlines.append('}')
264 outlines.append('')
265
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500266 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267 return False
268
269
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500270def validate_pv(pv):
271 if not pv or '_version' in pv.lower() or pv[0] not in '0123456789':
272 return False
273 return True
274
275def determine_from_filename(srcfile):
276 """Determine name and version from a filename"""
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600277 if is_package(srcfile):
278 # Force getting the value from the package metadata
279 return None, None
280
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500281 if '.tar.' in srcfile:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600282 namepart = srcfile.split('.tar.')[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500283 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600284 namepart = os.path.splitext(srcfile)[0]
285 namepart = namepart.lower().replace('_', '-')
286 if namepart.endswith('.src'):
287 namepart = namepart[:-4]
288 if namepart.endswith('.orig'):
289 namepart = namepart[:-5]
290 splitval = namepart.split('-')
291 logger.debug('determine_from_filename: split name %s into: %s' % (srcfile, splitval))
292
293 ver_re = re.compile('^v?[0-9]')
294
295 pv = None
296 pn = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500297 if len(splitval) == 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600298 # Try to split the version out if there is no separator (or a .)
299 res = re.match('^([^0-9]+)([0-9.]+.*)$', namepart)
300 if res:
301 if len(res.group(1)) > 1 and len(res.group(2)) > 1:
302 pn = res.group(1).rstrip('.')
303 pv = res.group(2)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500304 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305 pn = namepart
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500306 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307 if splitval[-1] in ['source', 'src']:
308 splitval.pop()
309 if len(splitval) > 2 and re.match('^(alpha|beta|stable|release|rc[0-9]|pre[0-9]|p[0-9]|[0-9]{8})', splitval[-1]) and ver_re.match(splitval[-2]):
310 pv = '-'.join(splitval[-2:])
311 if pv.endswith('-release'):
312 pv = pv[:-8]
313 splitval = splitval[:-2]
314 elif ver_re.match(splitval[-1]):
315 pv = splitval.pop()
316 pn = '-'.join(splitval)
317 if pv and pv.startswith('v'):
318 pv = pv[1:]
319 logger.debug('determine_from_filename: name = "%s" version = "%s"' % (pn, pv))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500320 return (pn, pv)
321
322def determine_from_url(srcuri):
323 """Determine name and version from a URL"""
324 pn = None
325 pv = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600326 parseres = urlparse(srcuri.lower().split(';', 1)[0])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500327 if parseres.path:
328 if 'github.com' in parseres.netloc:
329 res = re.search(r'.*/(.*?)/archive/(.*)-final\.(tar|zip)', parseres.path)
330 if res:
331 pn = res.group(1).strip().replace('_', '-')
332 pv = res.group(2).strip().replace('_', '.')
333 else:
334 res = re.search(r'.*/(.*?)/archive/v?(.*)\.(tar|zip)', parseres.path)
335 if res:
336 pn = res.group(1).strip().replace('_', '-')
337 pv = res.group(2).strip().replace('_', '.')
338 elif 'bitbucket.org' in parseres.netloc:
339 res = re.search(r'.*/(.*?)/get/[a-zA-Z_-]*([0-9][0-9a-zA-Z_.]*)\.(tar|zip)', parseres.path)
340 if res:
341 pn = res.group(1).strip().replace('_', '-')
342 pv = res.group(2).strip().replace('_', '.')
343
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500344 if not pn and not pv:
345 if parseres.scheme not in ['git', 'gitsm', 'svn', 'hg']:
346 srcfile = os.path.basename(parseres.path.rstrip('/'))
347 pn, pv = determine_from_filename(srcfile)
348 elif parseres.scheme in ['git', 'gitsm']:
349 pn = os.path.basename(parseres.path.rstrip('/')).lower().replace('_', '-')
350 if pn.endswith('.git'):
351 pn = pn[:-4]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500352
353 logger.debug('Determined from source URL: name = "%s", version = "%s"' % (pn, pv))
354 return (pn, pv)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500355
356def supports_srcrev(uri):
357 localdata = bb.data.createCopy(tinfoil.config_data)
358 # This is a bit sad, but if you don't have this set there can be some
359 # odd interactions with the urldata cache which lead to errors
360 localdata.setVar('SRCREV', '${AUTOREV}')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600361 try:
362 fetcher = bb.fetch2.Fetch([uri], localdata)
363 urldata = fetcher.ud
364 for u in urldata:
365 if urldata[u].method.supports_srcrev():
366 return True
367 except bb.fetch2.FetchError as e:
368 logger.debug('FetchError in supports_srcrev: %s' % str(e))
369 # Fall back to basic check
370 if uri.startswith(('git://', 'gitsm://')):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500371 return True
372 return False
373
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500374def reformat_git_uri(uri):
375 '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
376 checkuri = uri.split(';', 1)[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600377 if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://github.com/[^/]+/[^/]+/?$', checkuri):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500378 # Appends scheme if the scheme is missing
379 if not '://' in uri:
380 uri = 'git://' + uri
381 scheme, host, path, user, pswd, parms = bb.fetch2.decodeurl(uri)
382 # Detection mechanism, this is required due to certain URL are formatter with ":" rather than "/"
383 # which causes decodeurl to fail getting the right host and path
384 if len(host.split(':')) > 1:
385 splitslash = host.split(':')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400386 # Port number should not be split from host
387 if not re.match('^[0-9]+$', splitslash[1]):
388 host = splitslash[0]
389 path = '/' + splitslash[1] + path
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500390 #Algorithm:
391 # if user is defined, append protocol=ssh or if a protocol is defined, then honor the user-defined protocol
392 # if no user & password is defined, check for scheme type and append the protocol with the scheme type
393 # finally if protocols or if the url is well-formed, do nothing and rejoin everything back to normal
394 # Need to repackage the arguments for encodeurl, the format is: (scheme, host, path, user, password, OrderedDict([('key', 'value')]))
395 if user:
396 if not 'protocol' in parms:
397 parms.update({('protocol', 'ssh')})
398 elif (scheme == "http" or scheme == 'https' or scheme == 'ssh') and not ('protocol' in parms):
399 parms.update({('protocol', scheme)})
400 # Always append 'git://'
401 fUrl = bb.fetch2.encodeurl(('git', host, path, user, pswd, parms))
402 return fUrl
403 else:
404 return uri
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500405
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600406def is_package(url):
407 '''Check if a URL points to a package'''
408 checkurl = url.split(';', 1)[0]
409 if checkurl.endswith(('.deb', '.ipk', '.rpm', '.srpm')):
410 return True
411 return False
412
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500413def create_recipe(args):
414 import bb.process
415 import tempfile
416 import shutil
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600417 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500418
419 pkgarch = ""
420 if args.machine:
421 pkgarch = "${MACHINE_ARCH}"
422
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600423 extravalues = {}
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500424 checksums = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425 tempsrc = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600426 source = args.source
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500427 srcsubdir = ''
428 srcrev = '${AUTOREV}'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500429 srcbranch = ''
430 scheme = ''
431 storeTagName = ''
432 pv_srcpv = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600433
434 if os.path.isfile(source):
435 source = 'file://%s' % os.path.abspath(source)
436
437 if scriptutils.is_src_url(source):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400438 # Warn about github archive URLs
439 if re.match('https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source):
440 logger.warn('github archive files are not guaranteed to be stable and may be re-generated over time. If the latter occurs, the checksums will likely change and the recipe will fail at do_fetch. It is recommended that you point to an actual commit or tag in the repository instead (using the repository URL in conjunction with the -S/--srcrev option).')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500441 # Fetch a URL
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600442 fetchuri = reformat_git_uri(urldefrag(source)[0])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500443 if args.binary:
444 # Assume the archive contains the directory structure verbatim
445 # so we need to extract to a subdirectory
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600446 fetchuri += ';subdir=${BP}'
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500447 srcuri = fetchuri
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500448 rev_re = re.compile(';rev=([^;]+)')
449 res = rev_re.search(srcuri)
450 if res:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500451 if args.srcrev:
452 logger.error('rev= parameter and -S/--srcrev option cannot both be specified - use one or the other')
453 sys.exit(1)
454 if args.autorev:
455 logger.error('rev= parameter and -a/--autorev option cannot both be specified - use one or the other')
456 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500457 srcrev = res.group(1)
458 srcuri = rev_re.sub('', srcuri)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500459 elif args.srcrev:
460 srcrev = args.srcrev
461
462 # Check whether users provides any branch info in fetchuri.
463 # If true, we will skip all branch checking process to honor all user's input.
464 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(fetchuri)
465 srcbranch = params.get('branch')
466 if args.srcbranch:
467 if srcbranch:
468 logger.error('branch= parameter and -B/--srcbranch option cannot both be specified - use one or the other')
469 sys.exit(1)
470 srcbranch = args.srcbranch
471 nobranch = params.get('nobranch')
472 if nobranch and srcbranch:
473 logger.error('nobranch= cannot be used if you specify a branch')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500474 sys.exit(1)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500475 tag = params.get('tag')
476 if not srcbranch and not nobranch and srcrev != '${AUTOREV}':
477 # Append nobranch=1 in the following conditions:
478 # 1. User did not set 'branch=' in srcuri, and
479 # 2. User did not set 'nobranch=1' in srcuri, and
480 # 3. Source revision is not '${AUTOREV}'
481 params['nobranch'] = '1'
482 if tag:
483 # Keep a copy of tag and append nobranch=1 then remove tag from URL.
484 # Bitbake fetcher unable to fetch when {AUTOREV} and tag is set at the same time.
485 storeTagName = params['tag']
486 params['nobranch'] = '1'
487 del params['tag']
488 if scheme == 'npm':
489 params['noverify'] = '1'
490 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
491
492 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
493 bb.utils.mkdirhier(tmpparent)
494 tempsrc = tempfile.mkdtemp(prefix='recipetool-', dir=tmpparent)
495 srctree = os.path.join(tempsrc, 'source')
496
497 try:
498 checksums, ftmpdir = scriptutils.fetch_url(tinfoil, fetchuri, srcrev, srctree, logger, preserve_tmp=args.keep_temp)
499 except scriptutils.FetchUrlFailure as e:
500 logger.error(str(e))
501 sys.exit(1)
502
503 if ftmpdir and args.keep_temp:
504 logger.info('Fetch temp directory is %s' % ftmpdir)
505
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500506 dirlist = os.listdir(srctree)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500507 filterout = ['git.indirectionsymlink']
508 dirlist = [x for x in dirlist if x not in filterout]
509 logger.debug('Directory listing (excluding filtered out):\n %s' % '\n '.join(dirlist))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500510 if len(dirlist) == 1:
511 singleitem = os.path.join(srctree, dirlist[0])
512 if os.path.isdir(singleitem):
513 # We unpacked a single directory, so we should use that
514 srcsubdir = dirlist[0]
515 srctree = os.path.join(srctree, srcsubdir)
516 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500517 check_single_file(dirlist[0], fetchuri)
518 elif len(dirlist) == 0:
519 if '/' in fetchuri:
520 fn = os.path.join(tinfoil.config_data.getVar('DL_DIR'), fetchuri.split('/')[-1])
521 if os.path.isfile(fn):
522 check_single_file(fn, fetchuri)
523 # If we've got to here then there's no source so we might as well give up
524 logger.error('URL %s resulted in an empty source tree' % fetchuri)
525 sys.exit(1)
526
527 # We need this checking mechanism to improve the recipe created by recipetool and devtool
528 # is able to parse and build by bitbake.
529 # If there is no input for branch name, then check for branch name with SRCREV provided.
530 if not srcbranch and not nobranch and srcrev and (srcrev != '${AUTOREV}') and scheme in ['git', 'gitsm']:
531 try:
532 cmd = 'git branch -r --contains'
533 check_branch, check_branch_err = bb.process.run('%s %s' % (cmd, srcrev), cwd=srctree)
534 except bb.process.ExecutionError as err:
535 logger.error(str(err))
536 sys.exit(1)
537 get_branch = [x.strip() for x in check_branch.splitlines()]
538 # Remove HEAD reference point and drop remote prefix
539 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
540 if 'master' in get_branch:
541 # If it is master, we do not need to append 'branch=master' as this is default.
542 # Even with the case where get_branch has multiple objects, if 'master' is one
543 # of them, we should default take from 'master'
544 srcbranch = ''
545 elif len(get_branch) == 1:
546 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
547 srcbranch = get_branch[0]
548 else:
549 # If get_branch contains more than one objects, then display error and exit.
550 mbrch = '\n ' + '\n '.join(get_branch)
551 logger.error('Revision %s was found on multiple branches: %s\nPlease provide the correct branch with -B/--srcbranch' % (srcrev, mbrch))
552 sys.exit(1)
553
554 # Since we might have a value in srcbranch, we need to
555 # recontruct the srcuri to include 'branch' in params.
556 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
557 if srcbranch:
558 params['branch'] = srcbranch
559
560 if storeTagName and scheme in ['git', 'gitsm']:
561 # Check srcrev using tag and check validity of the tag
562 cmd = ('git rev-parse --verify %s' % (storeTagName))
563 try:
564 check_tag, check_tag_err = bb.process.run('%s' % cmd, cwd=srctree)
565 srcrev = check_tag.split()[0]
566 except bb.process.ExecutionError as err:
567 logger.error(str(err))
568 logger.error("Possibly wrong tag name is provided")
569 sys.exit(1)
570 # Drop tag from srcuri as it will have conflicts with SRCREV during recipe parse.
571 del params['tag']
572 srcuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
573
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600574 if os.path.exists(os.path.join(srctree, '.gitmodules')) and srcuri.startswith('git://'):
575 srcuri = 'gitsm://' + srcuri[6:]
576 logger.info('Fetching submodules...')
577 bb.process.run('git submodule update --init --recursive', cwd=srctree)
578
579 if is_package(fetchuri):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500580 localdata = bb.data.createCopy(tinfoil.config_data)
581 pkgfile = bb.fetch2.localpath(fetchuri, localdata)
582 if pkgfile:
583 tmpfdir = tempfile.mkdtemp(prefix='recipetool-')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600584 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600585 if pkgfile.endswith(('.deb', '.ipk')):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500586 stdout, _ = bb.process.run('ar x %s' % pkgfile, cwd=tmpfdir)
587 stdout, _ = bb.process.run('tar xf control.tar.gz', cwd=tmpfdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600588 values = convert_debian(tmpfdir)
589 extravalues.update(values)
590 elif pkgfile.endswith(('.rpm', '.srpm')):
591 stdout, _ = bb.process.run('rpm -qp --xml %s > pkginfo.xml' % pkgfile, cwd=tmpfdir)
592 values = convert_rpm_xml(os.path.join(tmpfdir, 'pkginfo.xml'))
593 extravalues.update(values)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500594 finally:
595 shutil.rmtree(tmpfdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500596 else:
597 # Assume we're pointing to an existing source tree
598 if args.extract_to:
599 logger.error('--extract-to cannot be specified if source is a directory')
600 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600601 if not os.path.isdir(source):
602 logger.error('Invalid source directory %s' % source)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600604 srctree = source
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500605 srcuri = ''
606 if os.path.exists(os.path.join(srctree, '.git')):
607 # Try to get upstream repo location from origin remote
608 try:
609 stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
610 except bb.process.ExecutionError as e:
611 stdout = None
612 if stdout:
613 for line in stdout.splitlines():
614 splitline = line.split()
615 if len(splitline) > 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600616 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500617 srcuri = reformat_git_uri(splitline[1])
618 srcsubdir = 'git'
619 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500620
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500621 if args.src_subdir:
622 srcsubdir = os.path.join(srcsubdir, args.src_subdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500623 srctree_use = os.path.abspath(os.path.join(srctree, args.src_subdir))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500624 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500625 srctree_use = os.path.abspath(srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500626
627 if args.outfile and os.path.isdir(args.outfile):
628 outfile = None
629 outdir = args.outfile
630 else:
631 outfile = args.outfile
632 outdir = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633 if outfile and outfile != '-':
634 if os.path.exists(outfile):
635 logger.error('Output file %s already exists' % outfile)
636 sys.exit(1)
637
638 lines_before = []
639 lines_after = []
640
641 lines_before.append('# Recipe created by %s' % os.path.basename(sys.argv[0]))
642 lines_before.append('# This is the basis of a recipe and may need further editing in order to be fully functional.')
643 lines_before.append('# (Feel free to remove these comments when editing.)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600644 # We need a blank line here so that patch_recipe_lines can rewind before the LICENSE comments
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500645 lines_before.append('')
646
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500647 # We'll come back and replace this later in handle_license_vars()
648 lines_before.append('##LICENSE_PLACEHOLDER##')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600649
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500650 handled = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500651 classes = []
652
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500653 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500654 pn = None
655 pv = None
656 if outfile:
657 recipefn = os.path.splitext(os.path.basename(outfile))[0]
658 fnsplit = recipefn.split('_')
659 if len(fnsplit) > 1:
660 pn = fnsplit[0]
661 pv = fnsplit[1]
662 else:
663 pn = recipefn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500664
665 if args.version:
666 pv = args.version
667
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500668 if args.name:
669 pn = args.name
670 if args.name.endswith('-native'):
671 if args.also_native:
672 logger.error('--also-native cannot be specified for a recipe named *-native (*-native denotes a recipe that is already only for native) - either remove the -native suffix from the name or drop --also-native')
673 sys.exit(1)
674 classes.append('native')
675 elif args.name.startswith('nativesdk-'):
676 if args.also_native:
677 logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
678 sys.exit(1)
679 classes.append('nativesdk')
680
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500681 if pv and pv not in 'git svn hg'.split():
682 realpv = pv
683 else:
684 realpv = None
685
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500686 if not srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500687 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
688 lines_before.append('SRC_URI = "%s"' % srcuri)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500689 for key, value in sorted(checksums.items()):
690 lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500691 if srcuri and supports_srcrev(srcuri):
692 lines_before.append('')
693 lines_before.append('# Modify these as desired')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500694 # Note: we have code to replace realpv further down if it gets set to some other value
695 scheme, _, _, _, _, _ = bb.fetch2.decodeurl(srcuri)
696 if scheme in ['git', 'gitsm']:
697 srcpvprefix = 'git'
698 elif scheme == 'svn':
699 srcpvprefix = 'svnr'
700 else:
701 srcpvprefix = scheme
702 lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix))
703 pv_srcpv = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600704 if not args.autorev and srcrev == '${AUTOREV}':
705 if os.path.exists(os.path.join(srctree, '.git')):
706 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
707 srcrev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500708 lines_before.append('SRCREV = "%s"' % srcrev)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500709 if args.provides:
710 lines_before.append('PROVIDES = "%s"' % args.provides)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711 lines_before.append('')
712
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600713 if srcsubdir and not args.binary:
714 # (for binary packages we explicitly specify subdir= when fetching to
715 # match the default value of S, so we don't need to set it in that case)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716 lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
717 lines_before.append('')
718
719 if pkgarch:
720 lines_after.append('PACKAGE_ARCH = "%s"' % pkgarch)
721 lines_after.append('')
722
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500723 if args.binary:
724 lines_after.append('INSANE_SKIP_${PN} += "already-stripped"')
725 lines_after.append('')
726
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500727 if args.fetch_dev:
728 extravalues['fetchdev'] = True
729 else:
730 extravalues['fetchdev'] = None
731
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500732 # Find all plugins that want to register handlers
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500733 logger.debug('Loading recipe handlers')
734 raw_handlers = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500735 for plugin in plugins:
736 if hasattr(plugin, 'register_recipe_handlers'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500737 plugin.register_recipe_handlers(raw_handlers)
738 # Sort handlers by priority
739 handlers = []
740 for i, handler in enumerate(raw_handlers):
741 if isinstance(handler, tuple):
742 handlers.append((handler[0], handler[1], i))
743 else:
744 handlers.append((handler, 0, i))
745 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
746 for handler, priority, _ in handlers:
747 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500748 setattr(handler, '_devtool', args.devtool)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500749 handlers = [item[0] for item in handlers]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500750
751 # Apply the handlers
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500752 if args.binary:
753 classes.append('bin_package')
754 handled.append('buildsystem')
755
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500756 for handler in handlers:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500757 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
758
759 extrafiles = extravalues.pop('extrafiles', {})
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600760 extra_pn = extravalues.pop('PN', None)
761 extra_pv = extravalues.pop('PV', None)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500762
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600763 if extra_pv and not realpv:
764 realpv = extra_pv
765 if not validate_pv(realpv):
766 realpv = None
767 else:
768 realpv = realpv.lower().split()[0]
769 if '_' in realpv:
770 realpv = realpv.replace('_', '-')
771 if extra_pn and not pn:
772 pn = extra_pn
773 if pn.startswith('GNU '):
774 pn = pn[4:]
775 if ' ' in pn:
776 # Probably a descriptive identifier rather than a proper name
777 pn = None
778 else:
779 pn = pn.lower()
780 if '_' in pn:
781 pn = pn.replace('_', '-')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500782
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500783 if srcuri and not realpv or not pn:
784 name_pn, name_pv = determine_from_url(srcuri)
785 if name_pn and not pn:
786 pn = name_pn
787 if name_pv and not realpv:
788 realpv = name_pv
789
790 licvalues = handle_license_vars(srctree_use, lines_before, handled, extravalues, tinfoil.config_data)
791
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500792 if not outfile:
793 if not pn:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500794 log_error_cond('Unable to determine short program name from source tree - please specify name with -N/--name or output file name with -o/--outfile', args.devtool)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500795 # devtool looks for this specific exit code, so don't change it
796 sys.exit(15)
797 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600798 if srcuri and srcuri.startswith(('gitsm://', 'git://', 'hg://', 'svn://')):
799 suffix = srcuri.split(':', 1)[0]
800 if suffix == 'gitsm':
801 suffix = 'git'
802 outfile = '%s_%s.bb' % (pn, suffix)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500803 elif realpv:
804 outfile = '%s_%s.bb' % (pn, realpv)
805 else:
806 outfile = '%s.bb' % pn
807 if outdir:
808 outfile = os.path.join(outdir, outfile)
809 # We need to check this again
810 if os.path.exists(outfile):
811 logger.error('Output file %s already exists' % outfile)
812 sys.exit(1)
813
814 # Move any extra files the plugins created to a directory next to the recipe
815 if extrafiles:
816 if outfile == '-':
817 extraoutdir = pn
818 else:
819 extraoutdir = os.path.join(os.path.dirname(outfile), pn)
820 bb.utils.mkdirhier(extraoutdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600821 for destfn, extrafile in extrafiles.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500822 shutil.move(extrafile, os.path.join(extraoutdir, destfn))
823
824 lines = lines_before
825 lines_before = []
826 skipblank = True
827 for line in lines:
828 if skipblank:
829 skipblank = False
830 if not line:
831 continue
832 if line.startswith('S = '):
833 if realpv and pv not in 'git svn hg'.split():
834 line = line.replace(realpv, '${PV}')
835 if pn:
836 line = line.replace(pn, '${BPN}')
837 if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
838 skipblank = True
839 continue
840 elif line.startswith('SRC_URI = '):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500841 if realpv and not pv_srcpv:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500842 line = line.replace(realpv, '${PV}')
843 elif line.startswith('PV = '):
844 if realpv:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500845 # Replace the first part of the PV value
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500846 line = re.sub('"[^+]*\+', '"%s+' % realpv, line)
847 lines_before.append(line)
848
849 if args.also_native:
850 lines = lines_after
851 lines_after = []
852 bbclassextend = None
853 for line in lines:
854 if line.startswith('BBCLASSEXTEND ='):
855 splitval = line.split('"')
856 if len(splitval) > 1:
857 bbclassextend = splitval[1].split()
858 if not 'native' in bbclassextend:
859 bbclassextend.insert(0, 'native')
860 line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
861 lines_after.append(line)
862 if not bbclassextend:
863 lines_after.append('BBCLASSEXTEND = "native"')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500864
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500865 postinst = ("postinst", extravalues.pop('postinst', None))
866 postrm = ("postrm", extravalues.pop('postrm', None))
867 preinst = ("preinst", extravalues.pop('preinst', None))
868 prerm = ("prerm", extravalues.pop('prerm', None))
869 funcs = [postinst, postrm, preinst, prerm]
870 for func in funcs:
871 if func[1]:
872 RecipeHandler.genfunction(lines_after, 'pkg_%s_${PN}' % func[0], func[1])
873
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874 outlines = []
875 outlines.extend(lines_before)
876 if classes:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500877 if outlines[-1] and not outlines[-1].startswith('#'):
878 outlines.append('')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500879 outlines.append('inherit %s' % ' '.join(classes))
880 outlines.append('')
881 outlines.extend(lines_after)
882
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600883 if extravalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600884 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False)
885
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500886 if args.extract_to:
887 scriptutils.git_convert_standalone_clone(srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500888 if os.path.isdir(args.extract_to):
889 # If the directory exists we'll move the temp dir into it instead of
890 # its contents - of course, we could try to always move its contents
891 # but that is a pain if there are symlinks; the simplest solution is
892 # to just remove it first
893 os.rmdir(args.extract_to)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500894 shutil.move(srctree, args.extract_to)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500895 if tempsrc == srctree:
896 tempsrc = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500897 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500898
899 if outfile == '-':
900 sys.stdout.write('\n'.join(outlines) + '\n')
901 else:
902 with open(outfile, 'w') as f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600903 lastline = None
904 for line in outlines:
905 if not lastline and not line:
906 # Skip extra blank lines
907 continue
908 f.write('%s\n' % line)
909 lastline = line
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500910 log_info_cond('Recipe %s has been created; further editing may be required to make it fully functional' % outfile, args.devtool)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500911
912 if tempsrc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600913 if args.keep_temp:
914 logger.info('Preserving temporary directory %s' % tempsrc)
915 else:
916 shutil.rmtree(tempsrc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500917
918 return 0
919
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500920def check_single_file(fn, fetchuri):
921 """Determine if a single downloaded file is something we can't handle"""
922 with open(fn, 'r', errors='surrogateescape') as f:
923 if '<html' in f.read(100).lower():
924 logger.error('Fetching "%s" returned a single HTML page - check the URL is correct and functional' % fetchuri)
925 sys.exit(1)
926
927def split_value(value):
928 if isinstance(value, str):
929 return value.split()
930 else:
931 return value
932
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600933def handle_license_vars(srctree, lines_before, handled, extravalues, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500934 lichandled = [x for x in handled if x[0] == 'license']
935 if lichandled:
936 # Someone else has already handled the license vars, just return their value
937 return lichandled[0][1]
938
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600939 licvalues = guess_license(srctree, d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500940 licenses = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600941 lic_files_chksum = []
942 lic_unknown = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500943 lines = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600944 if licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600945 for licvalue in licvalues:
946 if not licvalue[0] in licenses:
947 licenses.append(licvalue[0])
948 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
949 if licvalue[0] == 'Unknown':
950 lic_unknown.append(licvalue[1])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600951 if lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500952 lines.append('#')
953 lines.append('# The following license files were not able to be identified and are')
954 lines.append('# represented as "Unknown" below, you will need to check them yourself:')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600955 for licfile in lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500956 lines.append('# %s' % licfile)
957
958 extra_license = split_value(extravalues.pop('LICENSE', []))
959 if '&' in extra_license:
960 extra_license.remove('&')
961 if extra_license:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600962 if licenses == ['Unknown']:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500963 licenses = extra_license
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600964 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500965 for item in extra_license:
966 if item not in licenses:
967 licenses.append(item)
968 extra_lic_files_chksum = split_value(extravalues.pop('LIC_FILES_CHKSUM', []))
969 for item in extra_lic_files_chksum:
970 if item not in lic_files_chksum:
971 lic_files_chksum.append(item)
972
973 if lic_files_chksum:
974 # We are going to set the vars, so prepend the standard disclaimer
975 lines.insert(0, '# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is')
976 lines.insert(1, '# your responsibility to verify that the values are complete and correct.')
977 else:
978 # Without LIC_FILES_CHKSUM we set LICENSE = "CLOSED" to allow the
979 # user to get started easily
980 lines.append('# Unable to find any files that looked like license statements. Check the accompanying')
981 lines.append('# documentation and source headers and set LICENSE and LIC_FILES_CHKSUM accordingly.')
982 lines.append('#')
983 lines.append('# NOTE: LICENSE is being set to "CLOSED" to allow you to at least start building - if')
984 lines.append('# this is not accurate with respect to the licensing of the software being built (it')
985 lines.append('# will not be in most cases) you must specify the correct value before using this')
986 lines.append('# recipe for anything other than initial testing/development!')
987 licenses = ['CLOSED']
988
989 if extra_license and sorted(licenses) != sorted(extra_license):
990 lines.append('# NOTE: Original package / source metadata indicates license is: %s' % ' & '.join(extra_license))
991
992 if len(licenses) > 1:
993 lines.append('#')
994 lines.append('# NOTE: multiple licenses have been detected; they have been separated with &')
995 lines.append('# in the LICENSE value for now since it is a reasonable assumption that all')
996 lines.append('# of the licenses apply. If instead there is a choice between the multiple')
997 lines.append('# licenses then you should change the value to separate the licenses with |')
998 lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
999 lines.append('# to determine which situation is applicable.')
1000
1001 lines.append('LICENSE = "%s"' % ' & '.join(licenses))
1002 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
1003 lines.append('')
1004
1005 # Replace the placeholder so we get the values in the right place in the recipe file
1006 try:
1007 pos = lines_before.index('##LICENSE_PLACEHOLDER##')
1008 except ValueError:
1009 pos = -1
1010 if pos == -1:
1011 lines_before.extend(lines)
1012 else:
1013 lines_before[pos:pos+1] = lines
1014
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001015 handled.append(('license', licvalues))
1016 return licvalues
1017
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001018def get_license_md5sums(d, static_only=False):
1019 import bb.utils
1020 md5sums = {}
1021 if not static_only:
1022 # Gather md5sums of license files in common license dir
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001023 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001024 for fn in os.listdir(commonlicdir):
1025 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1026 md5sums[md5value] = fn
1027 # The following were extracted from common values in various recipes
1028 # (double checking the license against the license file itself, not just
1029 # the LICENSE value in the recipe)
1030 md5sums['94d55d512a9ba36caa9b7df079bae19f'] = 'GPLv2'
1031 md5sums['b234ee4d69f5fce4486a80fdaf4a4263'] = 'GPLv2'
1032 md5sums['59530bdf33659b29e73d4adb9f9f6552'] = 'GPLv2'
1033 md5sums['0636e73ff0215e8d672dc4c32c317bb3'] = 'GPLv2'
1034 md5sums['eb723b61539feef013de476e68b5c50a'] = 'GPLv2'
1035 md5sums['751419260aa954499f7abaabaa882bbe'] = 'GPLv2'
1036 md5sums['393a5ca445f6965873eca0259a17f833'] = 'GPLv2'
1037 md5sums['12f884d2ae1ff87c09e5b7ccc2c4ca7e'] = 'GPLv2'
1038 md5sums['8ca43cbc842c2336e835926c2166c28b'] = 'GPLv2'
1039 md5sums['ebb5c50ab7cab4baeffba14977030c07'] = 'GPLv2'
1040 md5sums['c93c0550bd3173f4504b2cbd8991e50b'] = 'GPLv2'
1041 md5sums['9ac2e7cff1ddaf48b6eab6028f23ef88'] = 'GPLv2'
1042 md5sums['4325afd396febcb659c36b49533135d4'] = 'GPLv2'
1043 md5sums['18810669f13b87348459e611d31ab760'] = 'GPLv2'
1044 md5sums['d7810fab7487fb0aad327b76f1be7cd7'] = 'GPLv2' # the Linux kernel's COPYING file
1045 md5sums['bbb461211a33b134d42ed5ee802b37ff'] = 'LGPLv2.1'
1046 md5sums['7fbc338309ac38fefcd64b04bb903e34'] = 'LGPLv2.1'
1047 md5sums['4fbd65380cdd255951079008b364516c'] = 'LGPLv2.1'
1048 md5sums['2d5025d4aa3495befef8f17206a5b0a1'] = 'LGPLv2.1'
1049 md5sums['fbc093901857fcd118f065f900982c24'] = 'LGPLv2.1'
1050 md5sums['a6f89e2100d9b6cdffcea4f398e37343'] = 'LGPLv2.1'
1051 md5sums['d8045f3b8f929c1cb29a1e3fd737b499'] = 'LGPLv2.1'
1052 md5sums['fad9b3332be894bab9bc501572864b29'] = 'LGPLv2.1'
1053 md5sums['3bf50002aefd002f49e7bb854063f7e7'] = 'LGPLv2'
1054 md5sums['9f604d8a4f8e74f4f5140845a21b6674'] = 'LGPLv2'
1055 md5sums['5f30f0716dfdd0d91eb439ebec522ec2'] = 'LGPLv2'
1056 md5sums['55ca817ccb7d5b5b66355690e9abc605'] = 'LGPLv2'
1057 md5sums['252890d9eee26aab7b432e8b8a616475'] = 'LGPLv2'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001058 md5sums['3214f080875748938ba060314b4f727d'] = 'LGPLv2'
1059 md5sums['db979804f025cf55aabec7129cb671ed'] = 'LGPLv2'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001060 md5sums['d32239bcb673463ab874e80d47fae504'] = 'GPLv3'
1061 md5sums['f27defe1e96c2e1ecd4e0c9be8967949'] = 'GPLv3'
1062 md5sums['6a6a8e020838b23406c81b19c1d46df6'] = 'LGPLv3'
1063 md5sums['3b83ef96387f14655fc854ddc3c6bd57'] = 'Apache-2.0'
1064 md5sums['385c55653886acac3821999a3ccd17b3'] = 'Artistic-1.0 | GPL-2.0' # some perl modules
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001065 md5sums['54c7042be62e169199200bc6477f04d1'] = 'BSD-3-Clause'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001066 return md5sums
1067
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001068def crunch_license(licfile):
1069 '''
1070 Remove non-material text from a license file and then check
1071 its md5sum against a known list. This works well for licenses
1072 which contain a copyright statement, but is also a useful way
1073 to handle people's insistence upon reformatting the license text
1074 slightly (with no material difference to the text of the
1075 license).
1076 '''
1077
1078 import oe.utils
1079
1080 # Note: these are carefully constructed!
1081 license_title_re = re.compile('^\(?(#+ *)?(The )?.{1,10} [Ll]icen[sc]e( \(.{1,10}\))?\)?:?$')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001082 license_statement_re = re.compile('^(This (project|software) is( free software)? (released|licen[sc]ed)|(Released|Licen[cs]ed)) under the .{1,10} [Ll]icen[sc]e:?$')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001083 copyright_re = re.compile('^(#+)? *Copyright .*$')
1084
1085 crunched_md5sums = {}
1086 # The following two were gleaned from the "forever" npm package
1087 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
1088 crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
1089 # https://github.com/vasi/pixz/blob/master/LICENSE
1090 crunched_md5sums['2f03392b40bbe663597b5bd3cc5ebdb9'] = 'BSD-2-Clause'
1091 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
1092 crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause'
1093 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
1094 crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2'
1095 # https://github.com/datto/dattobd/blob/master/COPYING
1096 # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT
1097 crunched_md5sums['1d65c5ad4bf6489f85f4812bf08ae73d'] = 'GPLv2'
1098 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
1099 # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD
1100 crunched_md5sums['fb530f66a7a89ce920f0e912b5b66d4b'] = 'GPLv2'
1101 # https://github.com/gkos/nrf24/blob/master/COPYING
1102 crunched_md5sums['7b6aaa4daeafdfa6ed5443fd2684581b'] = 'GPLv2'
1103 # https://github.com/josch09/resetusb/blob/master/COPYING
1104 crunched_md5sums['8b8ac1d631a4d220342e83bcf1a1fbc3'] = 'GPLv3'
1105 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
1106 crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1'
1107 # unixODBC-2.3.4 COPYING
1108 crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1'
1109 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
1110 crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3'
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001111 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1112 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
1113 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/edl-v10
1114 crunched_md5sums['0a9c78c0a398d1bbce4a166757d60387'] = 'EDL-1.0'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001115 lictext = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001116 with open(licfile, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001117 for line in f:
1118 # Drop opening statements
1119 if copyright_re.match(line):
1120 continue
1121 elif license_title_re.match(line):
1122 continue
1123 elif license_statement_re.match(line):
1124 continue
1125 # Squash spaces, and replace smart quotes, double quotes
1126 # and backticks with single quotes
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001127 line = oe.utils.squashspaces(line.strip())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001128 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
1129 if line:
1130 lictext.append(line)
1131
1132 m = hashlib.md5()
1133 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001134 m.update(' '.join(lictext).encode('utf-8'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001135 md5val = m.hexdigest()
1136 except UnicodeEncodeError:
1137 md5val = None
1138 lictext = ''
1139 license = crunched_md5sums.get(md5val, None)
1140 return license, md5val, lictext
1141
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001142def guess_license(srctree, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001143 import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001144 md5sums = get_license_md5sums(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001145
1146 licenses = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001147 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001148 licfiles = []
1149 for root, dirs, files in os.walk(srctree):
1150 for fn in files:
1151 for spec in licspecs:
1152 if fnmatch.fnmatch(fn, spec):
1153 fullpath = os.path.join(root, fn)
1154 if not fullpath in licfiles:
1155 licfiles.append(fullpath)
1156 for licfile in licfiles:
1157 md5value = bb.utils.md5_file(licfile)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001158 license = md5sums.get(md5value, None)
1159 if not license:
1160 license, crunched_md5, lictext = crunch_license(licfile)
1161 if not license:
1162 license = 'Unknown'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001163 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
1164
1165 # FIXME should we grab at least one source file with a license header and add that too?
1166
1167 return licenses
1168
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001169def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
1170 """
1171 Given a list of (license, path, md5sum) as returned by guess_license(),
1172 a dict of package name to path mappings, write out a set of
1173 package-specific LICENSE values.
1174 """
1175 pkglicenses = {pn: []}
1176 for license, licpath, _ in licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001177 for pkgname, pkgpath in packages.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001178 if licpath.startswith(pkgpath + '/'):
1179 if pkgname in pkglicenses:
1180 pkglicenses[pkgname].append(license)
1181 else:
1182 pkglicenses[pkgname] = [license]
1183 break
1184 else:
1185 # Accumulate on the main package
1186 pkglicenses[pn].append(license)
1187 outlicenses = {}
1188 for pkgname in packages:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001189 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001190 if license == 'Unknown' and pkgname in fallback_licenses:
1191 license = fallback_licenses[pkgname]
1192 outlines.append('LICENSE_%s = "%s"' % (pkgname, license))
1193 outlicenses[pkgname] = license.split()
1194 return outlicenses
1195
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001196def read_pkgconfig_provides(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001197 pkgdatadir = d.getVar('PKGDATA_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001198 pkgmap = {}
1199 for fn in glob.glob(os.path.join(pkgdatadir, 'shlibs2', '*.pclist')):
1200 with open(fn, 'r') as f:
1201 for line in f:
1202 pkgmap[os.path.basename(line.rstrip())] = os.path.splitext(os.path.basename(fn))[0]
1203 recipemap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001204 for pc, pkg in pkgmap.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001205 pkgdatafile = os.path.join(pkgdatadir, 'runtime', pkg)
1206 if os.path.exists(pkgdatafile):
1207 with open(pkgdatafile, 'r') as f:
1208 for line in f:
1209 if line.startswith('PN: '):
1210 recipemap[pc] = line.split(':', 1)[1].strip()
1211 return recipemap
1212
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001213def convert_debian(debpath):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001214 value_map = {'Package': 'PN',
1215 'Version': 'PV',
1216 'Section': 'SECTION',
1217 'License': 'LICENSE',
1218 'Homepage': 'HOMEPAGE'}
1219
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001220 # FIXME extend this mapping - perhaps use distro_alias.inc?
1221 depmap = {'libz-dev': 'zlib'}
1222
1223 values = {}
1224 depends = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001225 with open(os.path.join(debpath, 'control'), 'r', errors='surrogateescape') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001226 indesc = False
1227 for line in f:
1228 if indesc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001229 if line.startswith(' '):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001230 if line.startswith(' This package contains'):
1231 indesc = False
1232 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001233 if 'DESCRIPTION' in values:
1234 values['DESCRIPTION'] += ' ' + line.strip()
1235 else:
1236 values['DESCRIPTION'] = line.strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001237 else:
1238 indesc = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001239 if not indesc:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001240 splitline = line.split(':', 1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001241 if len(splitline) < 2:
1242 continue
1243 key = splitline[0]
1244 value = splitline[1].strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001245 if key == 'Build-Depends':
1246 for dep in value.split(','):
1247 dep = dep.split()[0]
1248 mapped = depmap.get(dep, '')
1249 if mapped:
1250 depends.append(mapped)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001251 elif key == 'Description':
1252 values['SUMMARY'] = value
1253 indesc = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001254 else:
1255 varname = value_map.get(key, None)
1256 if varname:
1257 values[varname] = value
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001258 postinst = os.path.join(debpath, 'postinst')
1259 postrm = os.path.join(debpath, 'postrm')
1260 preinst = os.path.join(debpath, 'preinst')
1261 prerm = os.path.join(debpath, 'prerm')
1262 sfiles = [postinst, postrm, preinst, prerm]
1263 for sfile in sfiles:
1264 if os.path.isfile(sfile):
1265 logger.info("Converting %s file to recipe function..." %
1266 os.path.basename(sfile).upper())
1267 content = []
1268 with open(sfile) as f:
1269 for line in f:
1270 if "#!/" in line:
1271 continue
1272 line = line.rstrip("\n")
1273 if line.strip():
1274 content.append(line)
1275 if content:
1276 values[os.path.basename(f.name)] = content
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001277
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001278 #if depends:
1279 # values['DEPENDS'] = ' '.join(depends)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001280
1281 return values
1282
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001283def convert_rpm_xml(xmlfile):
1284 '''Converts the output from rpm -qp --xml to a set of variable values'''
1285 import xml.etree.ElementTree as ElementTree
1286 rpmtag_map = {'Name': 'PN',
1287 'Version': 'PV',
1288 'Summary': 'SUMMARY',
1289 'Description': 'DESCRIPTION',
1290 'License': 'LICENSE',
1291 'Url': 'HOMEPAGE'}
1292
1293 values = {}
1294 tree = ElementTree.parse(xmlfile)
1295 root = tree.getroot()
1296 for child in root:
1297 if child.tag == 'rpmTag':
1298 name = child.attrib.get('name', None)
1299 if name:
1300 varname = rpmtag_map.get(name, None)
1301 if varname:
1302 values[varname] = child[0].text
1303 return values
1304
1305
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001306def register_commands(subparsers):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001307 parser_create = subparsers.add_parser('create',
1308 help='Create a new recipe',
1309 description='Creates a new recipe from a source tree')
1310 parser_create.add_argument('source', help='Path or URL to source')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001311 parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001312 parser_create.add_argument('-p', '--provides', help='Specify an alias for the item provided by the recipe')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001313 parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
1314 parser_create.add_argument('-x', '--extract-to', metavar='EXTRACTPATH', help='Assuming source is a URL, fetch it and extract it to the directory specified as %(metavar)s')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001315 parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001316 parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001317 parser_create.add_argument('-b', '--binary', help='Treat the source tree as something that should be installed verbatim (no compilation, same directory structure)', action='store_true')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001318 parser_create.add_argument('--also-native', help='Also add native variant (i.e. support building recipe for the build host as well as the target machine)', action='store_true')
1319 parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001320 group = parser_create.add_mutually_exclusive_group()
1321 group.add_argument('-a', '--autorev', help='When fetching from a git repository, set SRCREV in the recipe to a floating revision instead of fixed', action="store_true")
1322 group.add_argument('-S', '--srcrev', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
1323 parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001324 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001325 parser_create.add_argument('--fetch-dev', action="store_true", help='For npm, also fetch devDependencies')
1326 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001327 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1328 parser_create.set_defaults(func=create_recipe)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001329