blob: 6cbf4de6748deb01f9ad4a02c519c13acee90c3a [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#
Brad Bishopc342db32019-05-15 21:57:59 -04005# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007
8import sys
9import os
10import argparse
11import glob
12import fnmatch
13import re
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050014import json
Patrick Williamsc124f4f2015-09-15 14:41:29 -050015import logging
16import scriptutils
Patrick Williamsc0f7c042017-02-23 20:41:17 -060017from urllib.parse import urlparse, urldefrag, urlsplit
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050018import hashlib
Brad Bishop6e60e8b2018-02-01 10:27:11 -050019import bb.fetch2
Patrick Williamsc124f4f2015-09-15 14:41:29 -050020logger = logging.getLogger('recipetool')
21
22tinfoil = None
23plugins = None
24
Brad Bishop6e60e8b2018-02-01 10:27:11 -050025def log_error_cond(message, debugonly):
26 if debugonly:
27 logger.debug(message)
28 else:
29 logger.error(message)
30
31def log_info_cond(message, debugonly):
32 if debugonly:
33 logger.debug(message)
34 else:
35 logger.info(message)
36
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037def plugin_init(pluginlist):
38 # Take a reference to the list so we can use it later
39 global plugins
40 plugins = pluginlist
41
42def tinfoil_init(instance):
43 global tinfoil
44 tinfoil = instance
45
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050046class RecipeHandler(object):
47 recipelibmap = {}
48 recipeheadermap = {}
49 recipecmakefilemap = {}
50 recipebinmap = {}
51
Brad Bishop6e60e8b2018-02-01 10:27:11 -050052 def __init__(self):
53 self._devtool = False
54
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055 @staticmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050056 def load_libmap(d):
57 '''Load library->recipe mapping'''
58 import oe.package
59
60 if RecipeHandler.recipelibmap:
61 return
62 # First build up library->package mapping
Brad Bishop96ff1982019-08-19 13:50:42 -040063 d2 = bb.data.createCopy(d)
64 d2.setVar("WORKDIR_PKGDATA", "${PKGDATA_DIR}")
65 shlib_providers = oe.package.read_shlib_providers(d2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050066 libdir = d.getVar('libdir')
67 base_libdir = d.getVar('base_libdir')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050068 libpaths = list(set([base_libdir, libdir]))
69 libname_re = re.compile('^lib(.+)\.so.*$')
70 pkglibmap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -060071 for lib, item in shlib_providers.items():
72 for path, pkg in item.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050073 if path in libpaths:
74 res = libname_re.match(lib)
75 if res:
76 libname = res.group(1)
77 if not libname in pkglibmap:
78 pkglibmap[libname] = pkg[0]
79 else:
80 logger.debug('unable to extract library name from %s' % lib)
81
82 # Now turn it into a library->recipe mapping
Brad Bishop6e60e8b2018-02-01 10:27:11 -050083 pkgdata_dir = d.getVar('PKGDATA_DIR')
Patrick Williamsc0f7c042017-02-23 20:41:17 -060084 for libname, pkg in pkglibmap.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050085 try:
86 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
87 for line in f:
88 if line.startswith('PN:'):
89 RecipeHandler.recipelibmap[libname] = line.split(':', 1)[-1].strip()
90 break
91 except IOError as ioe:
92 if ioe.errno == 2:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080093 logger.warning('unable to find a pkgdata file for package %s' % pkg)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050094 else:
95 raise
96
97 # Some overrides - these should be mapped to the virtual
98 RecipeHandler.recipelibmap['GL'] = 'virtual/libgl'
99 RecipeHandler.recipelibmap['EGL'] = 'virtual/egl'
100 RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2'
101
102 @staticmethod
103 def load_devel_filemap(d):
104 '''Build up development file->recipe mapping'''
105 if RecipeHandler.recipeheadermap:
106 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500107 pkgdata_dir = d.getVar('PKGDATA_DIR')
108 includedir = d.getVar('includedir')
109 cmakedir = os.path.join(d.getVar('libdir'), 'cmake')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500110 for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')):
111 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
112 pn = None
113 headers = []
114 cmakefiles = []
115 for line in f:
116 if line.startswith('PN:'):
117 pn = line.split(':', 1)[-1].strip()
118 elif line.startswith('FILES_INFO:'):
119 val = line.split(':', 1)[1].strip()
120 dictval = json.loads(val)
121 for fullpth in sorted(dictval):
122 if fullpth.startswith(includedir) and fullpth.endswith('.h'):
123 headers.append(os.path.relpath(fullpth, includedir))
124 elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'):
125 cmakefiles.append(os.path.relpath(fullpth, cmakedir))
126 if pn and headers:
127 for header in headers:
128 RecipeHandler.recipeheadermap[header] = pn
129 if pn and cmakefiles:
130 for fn in cmakefiles:
131 RecipeHandler.recipecmakefilemap[fn] = pn
132
133 @staticmethod
134 def load_binmap(d):
135 '''Build up native binary->recipe mapping'''
136 if RecipeHandler.recipebinmap:
137 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500138 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
139 staging_bindir_native = d.getVar('STAGING_BINDIR_NATIVE')
140 build_arch = d.getVar('BUILD_ARCH')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500141 fileprefix = 'manifest-%s-' % build_arch
142 for fn in glob.glob(os.path.join(sstate_manifests, '%s*-native.populate_sysroot' % fileprefix)):
143 with open(fn, 'r') as f:
144 pn = os.path.basename(fn).rsplit('.', 1)[0][len(fileprefix):]
145 for line in f:
146 if line.startswith(staging_bindir_native):
147 prog = os.path.basename(line.rstrip())
148 RecipeHandler.recipebinmap[prog] = pn
149
150 @staticmethod
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500151 def checkfiles(path, speclist, recursive=False, excludedirs=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500152 results = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500153 if recursive:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500154 for root, dirs, files in os.walk(path, topdown=True):
155 if excludedirs:
156 dirs[:] = [d for d in dirs if d not in excludedirs]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500157 for fn in files:
158 for spec in speclist:
159 if fnmatch.fnmatch(fn, spec):
160 results.append(os.path.join(root, fn))
161 else:
162 for spec in speclist:
163 results.extend(glob.glob(os.path.join(path, spec)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500164 return results
165
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500166 @staticmethod
167 def handle_depends(libdeps, pcdeps, deps, outlines, values, d):
168 if pcdeps:
169 recipemap = read_pkgconfig_provides(d)
170 if libdeps:
171 RecipeHandler.load_libmap(d)
172
173 ignorelibs = ['socket']
174 ignoredeps = ['gcc-runtime', 'glibc', 'uclibc', 'musl', 'tar-native', 'binutils-native', 'coreutils-native']
175
176 unmappedpc = []
177 pcdeps = list(set(pcdeps))
178 for pcdep in pcdeps:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600179 if isinstance(pcdep, str):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500180 recipe = recipemap.get(pcdep, None)
181 if recipe:
182 deps.append(recipe)
183 else:
184 if not pcdep.startswith('$'):
185 unmappedpc.append(pcdep)
186 else:
187 for item in pcdep:
188 recipe = recipemap.get(pcdep, None)
189 if recipe:
190 deps.append(recipe)
191 break
192 else:
193 unmappedpc.append('(%s)' % ' or '.join(pcdep))
194
195 unmappedlibs = []
196 for libdep in libdeps:
197 if isinstance(libdep, tuple):
198 lib, header = libdep
199 else:
200 lib = libdep
201 header = None
202
203 if lib in ignorelibs:
204 logger.debug('Ignoring library dependency %s' % lib)
205 continue
206
207 recipe = RecipeHandler.recipelibmap.get(lib, None)
208 if recipe:
209 deps.append(recipe)
210 elif recipe is None:
211 if header:
212 RecipeHandler.load_devel_filemap(d)
213 recipe = RecipeHandler.recipeheadermap.get(header, None)
214 if recipe:
215 deps.append(recipe)
216 elif recipe is None:
217 unmappedlibs.append(lib)
218 else:
219 unmappedlibs.append(lib)
220
221 deps = set(deps).difference(set(ignoredeps))
222
223 if unmappedpc:
224 outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmappedpc))
225 outlines.append('# (this is based on recipes that have previously been built and packaged)')
226
227 if unmappedlibs:
228 outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmappedlibs))))
229 outlines.append('# (this is based on recipes that have previously been built and packaged)')
230
231 if deps:
232 values['DEPENDS'] = ' '.join(deps)
233
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500234 @staticmethod
235 def genfunction(outlines, funcname, content, python=False, forcespace=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500236 if python:
237 prefix = 'python '
238 else:
239 prefix = ''
240 outlines.append('%s%s () {' % (prefix, funcname))
241 if python or forcespace:
242 indent = ' '
243 else:
244 indent = '\t'
245 addnoop = not python
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500246 for line in content:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500247 outlines.append('%s%s' % (indent, line))
248 if addnoop:
249 strippedline = line.lstrip()
250 if strippedline and not strippedline.startswith('#'):
251 addnoop = False
252 if addnoop:
253 # Without this there'll be a syntax error
254 outlines.append('%s:' % indent)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500255 outlines.append('}')
256 outlines.append('')
257
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500258 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259 return False
260
261
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262def validate_pv(pv):
263 if not pv or '_version' in pv.lower() or pv[0] not in '0123456789':
264 return False
265 return True
266
267def determine_from_filename(srcfile):
268 """Determine name and version from a filename"""
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600269 if is_package(srcfile):
270 # Force getting the value from the package metadata
271 return None, None
272
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500273 if '.tar.' in srcfile:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600274 namepart = srcfile.split('.tar.')[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500275 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600276 namepart = os.path.splitext(srcfile)[0]
277 namepart = namepart.lower().replace('_', '-')
278 if namepart.endswith('.src'):
279 namepart = namepart[:-4]
280 if namepart.endswith('.orig'):
281 namepart = namepart[:-5]
282 splitval = namepart.split('-')
283 logger.debug('determine_from_filename: split name %s into: %s' % (srcfile, splitval))
284
285 ver_re = re.compile('^v?[0-9]')
286
287 pv = None
288 pn = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500289 if len(splitval) == 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600290 # Try to split the version out if there is no separator (or a .)
291 res = re.match('^([^0-9]+)([0-9.]+.*)$', namepart)
292 if res:
293 if len(res.group(1)) > 1 and len(res.group(2)) > 1:
294 pn = res.group(1).rstrip('.')
295 pv = res.group(2)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500296 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600297 pn = namepart
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500298 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600299 if splitval[-1] in ['source', 'src']:
300 splitval.pop()
301 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]):
302 pv = '-'.join(splitval[-2:])
303 if pv.endswith('-release'):
304 pv = pv[:-8]
305 splitval = splitval[:-2]
306 elif ver_re.match(splitval[-1]):
307 pv = splitval.pop()
308 pn = '-'.join(splitval)
309 if pv and pv.startswith('v'):
310 pv = pv[1:]
311 logger.debug('determine_from_filename: name = "%s" version = "%s"' % (pn, pv))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500312 return (pn, pv)
313
314def determine_from_url(srcuri):
315 """Determine name and version from a URL"""
316 pn = None
317 pv = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600318 parseres = urlparse(srcuri.lower().split(';', 1)[0])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500319 if parseres.path:
320 if 'github.com' in parseres.netloc:
321 res = re.search(r'.*/(.*?)/archive/(.*)-final\.(tar|zip)', parseres.path)
322 if res:
323 pn = res.group(1).strip().replace('_', '-')
324 pv = res.group(2).strip().replace('_', '.')
325 else:
326 res = re.search(r'.*/(.*?)/archive/v?(.*)\.(tar|zip)', parseres.path)
327 if res:
328 pn = res.group(1).strip().replace('_', '-')
329 pv = res.group(2).strip().replace('_', '.')
330 elif 'bitbucket.org' in parseres.netloc:
331 res = re.search(r'.*/(.*?)/get/[a-zA-Z_-]*([0-9][0-9a-zA-Z_.]*)\.(tar|zip)', parseres.path)
332 if res:
333 pn = res.group(1).strip().replace('_', '-')
334 pv = res.group(2).strip().replace('_', '.')
335
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500336 if not pn and not pv:
337 if parseres.scheme not in ['git', 'gitsm', 'svn', 'hg']:
338 srcfile = os.path.basename(parseres.path.rstrip('/'))
339 pn, pv = determine_from_filename(srcfile)
340 elif parseres.scheme in ['git', 'gitsm']:
341 pn = os.path.basename(parseres.path.rstrip('/')).lower().replace('_', '-')
342 if pn.endswith('.git'):
343 pn = pn[:-4]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500344
345 logger.debug('Determined from source URL: name = "%s", version = "%s"' % (pn, pv))
346 return (pn, pv)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500347
348def supports_srcrev(uri):
349 localdata = bb.data.createCopy(tinfoil.config_data)
350 # This is a bit sad, but if you don't have this set there can be some
351 # odd interactions with the urldata cache which lead to errors
352 localdata.setVar('SRCREV', '${AUTOREV}')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600353 try:
354 fetcher = bb.fetch2.Fetch([uri], localdata)
355 urldata = fetcher.ud
356 for u in urldata:
357 if urldata[u].method.supports_srcrev():
358 return True
359 except bb.fetch2.FetchError as e:
360 logger.debug('FetchError in supports_srcrev: %s' % str(e))
361 # Fall back to basic check
362 if uri.startswith(('git://', 'gitsm://')):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500363 return True
364 return False
365
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500366def reformat_git_uri(uri):
367 '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
368 checkuri = uri.split(';', 1)[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600369 if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://github.com/[^/]+/[^/]+/?$', checkuri):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500370 # Appends scheme if the scheme is missing
371 if not '://' in uri:
372 uri = 'git://' + uri
373 scheme, host, path, user, pswd, parms = bb.fetch2.decodeurl(uri)
374 # Detection mechanism, this is required due to certain URL are formatter with ":" rather than "/"
375 # which causes decodeurl to fail getting the right host and path
376 if len(host.split(':')) > 1:
377 splitslash = host.split(':')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400378 # Port number should not be split from host
379 if not re.match('^[0-9]+$', splitslash[1]):
380 host = splitslash[0]
381 path = '/' + splitslash[1] + path
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500382 #Algorithm:
383 # if user is defined, append protocol=ssh or if a protocol is defined, then honor the user-defined protocol
384 # if no user & password is defined, check for scheme type and append the protocol with the scheme type
385 # finally if protocols or if the url is well-formed, do nothing and rejoin everything back to normal
386 # Need to repackage the arguments for encodeurl, the format is: (scheme, host, path, user, password, OrderedDict([('key', 'value')]))
387 if user:
388 if not 'protocol' in parms:
389 parms.update({('protocol', 'ssh')})
390 elif (scheme == "http" or scheme == 'https' or scheme == 'ssh') and not ('protocol' in parms):
391 parms.update({('protocol', scheme)})
392 # Always append 'git://'
393 fUrl = bb.fetch2.encodeurl(('git', host, path, user, pswd, parms))
394 return fUrl
395 else:
396 return uri
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500397
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600398def is_package(url):
399 '''Check if a URL points to a package'''
400 checkurl = url.split(';', 1)[0]
401 if checkurl.endswith(('.deb', '.ipk', '.rpm', '.srpm')):
402 return True
403 return False
404
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500405def create_recipe(args):
406 import bb.process
407 import tempfile
408 import shutil
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600409 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500410
411 pkgarch = ""
412 if args.machine:
413 pkgarch = "${MACHINE_ARCH}"
414
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600415 extravalues = {}
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500416 checksums = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500417 tempsrc = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600418 source = args.source
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500419 srcsubdir = ''
420 srcrev = '${AUTOREV}'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500421 srcbranch = ''
422 scheme = ''
423 storeTagName = ''
424 pv_srcpv = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600425
426 if os.path.isfile(source):
427 source = 'file://%s' % os.path.abspath(source)
428
429 if scriptutils.is_src_url(source):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400430 # Warn about github archive URLs
431 if re.match('https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800432 logger.warning('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 -0500433 # Fetch a URL
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600434 fetchuri = reformat_git_uri(urldefrag(source)[0])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500435 if args.binary:
436 # Assume the archive contains the directory structure verbatim
437 # so we need to extract to a subdirectory
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600438 fetchuri += ';subdir=${BP}'
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500439 srcuri = fetchuri
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500440 rev_re = re.compile(';rev=([^;]+)')
441 res = rev_re.search(srcuri)
442 if res:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500443 if args.srcrev:
444 logger.error('rev= parameter and -S/--srcrev option cannot both be specified - use one or the other')
445 sys.exit(1)
446 if args.autorev:
447 logger.error('rev= parameter and -a/--autorev option cannot both be specified - use one or the other')
448 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500449 srcrev = res.group(1)
450 srcuri = rev_re.sub('', srcuri)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500451 elif args.srcrev:
452 srcrev = args.srcrev
453
454 # Check whether users provides any branch info in fetchuri.
455 # If true, we will skip all branch checking process to honor all user's input.
456 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(fetchuri)
457 srcbranch = params.get('branch')
458 if args.srcbranch:
459 if srcbranch:
460 logger.error('branch= parameter and -B/--srcbranch option cannot both be specified - use one or the other')
461 sys.exit(1)
462 srcbranch = args.srcbranch
463 nobranch = params.get('nobranch')
464 if nobranch and srcbranch:
465 logger.error('nobranch= cannot be used if you specify a branch')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500466 sys.exit(1)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500467 tag = params.get('tag')
468 if not srcbranch and not nobranch and srcrev != '${AUTOREV}':
469 # Append nobranch=1 in the following conditions:
470 # 1. User did not set 'branch=' in srcuri, and
471 # 2. User did not set 'nobranch=1' in srcuri, and
472 # 3. Source revision is not '${AUTOREV}'
473 params['nobranch'] = '1'
474 if tag:
475 # Keep a copy of tag and append nobranch=1 then remove tag from URL.
476 # Bitbake fetcher unable to fetch when {AUTOREV} and tag is set at the same time.
477 storeTagName = params['tag']
478 params['nobranch'] = '1'
479 del params['tag']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500480 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
481
482 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
483 bb.utils.mkdirhier(tmpparent)
484 tempsrc = tempfile.mkdtemp(prefix='recipetool-', dir=tmpparent)
485 srctree = os.path.join(tempsrc, 'source')
486
487 try:
488 checksums, ftmpdir = scriptutils.fetch_url(tinfoil, fetchuri, srcrev, srctree, logger, preserve_tmp=args.keep_temp)
489 except scriptutils.FetchUrlFailure as e:
490 logger.error(str(e))
491 sys.exit(1)
492
493 if ftmpdir and args.keep_temp:
494 logger.info('Fetch temp directory is %s' % ftmpdir)
495
Brad Bishop6dbb3162019-11-25 09:41:34 -0500496 dirlist = scriptutils.filter_src_subdirs(srctree)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500497 logger.debug('Directory listing (excluding filtered out):\n %s' % '\n '.join(dirlist))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500498 if len(dirlist) == 1:
499 singleitem = os.path.join(srctree, dirlist[0])
500 if os.path.isdir(singleitem):
501 # We unpacked a single directory, so we should use that
502 srcsubdir = dirlist[0]
503 srctree = os.path.join(srctree, srcsubdir)
504 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500505 check_single_file(dirlist[0], fetchuri)
506 elif len(dirlist) == 0:
507 if '/' in fetchuri:
508 fn = os.path.join(tinfoil.config_data.getVar('DL_DIR'), fetchuri.split('/')[-1])
509 if os.path.isfile(fn):
510 check_single_file(fn, fetchuri)
511 # If we've got to here then there's no source so we might as well give up
512 logger.error('URL %s resulted in an empty source tree' % fetchuri)
513 sys.exit(1)
514
515 # We need this checking mechanism to improve the recipe created by recipetool and devtool
516 # is able to parse and build by bitbake.
517 # If there is no input for branch name, then check for branch name with SRCREV provided.
518 if not srcbranch and not nobranch and srcrev and (srcrev != '${AUTOREV}') and scheme in ['git', 'gitsm']:
519 try:
520 cmd = 'git branch -r --contains'
521 check_branch, check_branch_err = bb.process.run('%s %s' % (cmd, srcrev), cwd=srctree)
522 except bb.process.ExecutionError as err:
523 logger.error(str(err))
524 sys.exit(1)
525 get_branch = [x.strip() for x in check_branch.splitlines()]
526 # Remove HEAD reference point and drop remote prefix
527 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
528 if 'master' in get_branch:
529 # If it is master, we do not need to append 'branch=master' as this is default.
530 # Even with the case where get_branch has multiple objects, if 'master' is one
531 # of them, we should default take from 'master'
532 srcbranch = ''
533 elif len(get_branch) == 1:
534 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
535 srcbranch = get_branch[0]
536 else:
537 # If get_branch contains more than one objects, then display error and exit.
538 mbrch = '\n ' + '\n '.join(get_branch)
539 logger.error('Revision %s was found on multiple branches: %s\nPlease provide the correct branch with -B/--srcbranch' % (srcrev, mbrch))
540 sys.exit(1)
541
542 # Since we might have a value in srcbranch, we need to
543 # recontruct the srcuri to include 'branch' in params.
544 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
545 if srcbranch:
546 params['branch'] = srcbranch
547
548 if storeTagName and scheme in ['git', 'gitsm']:
549 # Check srcrev using tag and check validity of the tag
550 cmd = ('git rev-parse --verify %s' % (storeTagName))
551 try:
552 check_tag, check_tag_err = bb.process.run('%s' % cmd, cwd=srctree)
553 srcrev = check_tag.split()[0]
554 except bb.process.ExecutionError as err:
555 logger.error(str(err))
556 logger.error("Possibly wrong tag name is provided")
557 sys.exit(1)
558 # Drop tag from srcuri as it will have conflicts with SRCREV during recipe parse.
559 del params['tag']
560 srcuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
561
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600562 if os.path.exists(os.path.join(srctree, '.gitmodules')) and srcuri.startswith('git://'):
563 srcuri = 'gitsm://' + srcuri[6:]
564 logger.info('Fetching submodules...')
565 bb.process.run('git submodule update --init --recursive', cwd=srctree)
566
567 if is_package(fetchuri):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500568 localdata = bb.data.createCopy(tinfoil.config_data)
569 pkgfile = bb.fetch2.localpath(fetchuri, localdata)
570 if pkgfile:
571 tmpfdir = tempfile.mkdtemp(prefix='recipetool-')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600572 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600573 if pkgfile.endswith(('.deb', '.ipk')):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500574 stdout, _ = bb.process.run('ar x %s' % pkgfile, cwd=tmpfdir)
575 stdout, _ = bb.process.run('tar xf control.tar.gz', cwd=tmpfdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600576 values = convert_debian(tmpfdir)
577 extravalues.update(values)
578 elif pkgfile.endswith(('.rpm', '.srpm')):
579 stdout, _ = bb.process.run('rpm -qp --xml %s > pkginfo.xml' % pkgfile, cwd=tmpfdir)
580 values = convert_rpm_xml(os.path.join(tmpfdir, 'pkginfo.xml'))
581 extravalues.update(values)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500582 finally:
583 shutil.rmtree(tmpfdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500584 else:
585 # Assume we're pointing to an existing source tree
586 if args.extract_to:
587 logger.error('--extract-to cannot be specified if source is a directory')
588 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600589 if not os.path.isdir(source):
590 logger.error('Invalid source directory %s' % source)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600592 srctree = source
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500593 srcuri = ''
594 if os.path.exists(os.path.join(srctree, '.git')):
595 # Try to get upstream repo location from origin remote
596 try:
597 stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
598 except bb.process.ExecutionError as e:
599 stdout = None
600 if stdout:
601 for line in stdout.splitlines():
602 splitline = line.split()
603 if len(splitline) > 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600604 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500605 srcuri = reformat_git_uri(splitline[1])
606 srcsubdir = 'git'
607 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500608
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500609 if args.src_subdir:
610 srcsubdir = os.path.join(srcsubdir, args.src_subdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500611 srctree_use = os.path.abspath(os.path.join(srctree, args.src_subdir))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500612 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500613 srctree_use = os.path.abspath(srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500614
615 if args.outfile and os.path.isdir(args.outfile):
616 outfile = None
617 outdir = args.outfile
618 else:
619 outfile = args.outfile
620 outdir = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500621 if outfile and outfile != '-':
622 if os.path.exists(outfile):
623 logger.error('Output file %s already exists' % outfile)
624 sys.exit(1)
625
626 lines_before = []
627 lines_after = []
628
629 lines_before.append('# Recipe created by %s' % os.path.basename(sys.argv[0]))
630 lines_before.append('# This is the basis of a recipe and may need further editing in order to be fully functional.')
631 lines_before.append('# (Feel free to remove these comments when editing.)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600632 # We need a blank line here so that patch_recipe_lines can rewind before the LICENSE comments
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633 lines_before.append('')
634
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500635 # We'll come back and replace this later in handle_license_vars()
636 lines_before.append('##LICENSE_PLACEHOLDER##')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600637
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500638 handled = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500639 classes = []
640
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500641 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500642 pn = None
643 pv = None
644 if outfile:
645 recipefn = os.path.splitext(os.path.basename(outfile))[0]
646 fnsplit = recipefn.split('_')
647 if len(fnsplit) > 1:
648 pn = fnsplit[0]
649 pv = fnsplit[1]
650 else:
651 pn = recipefn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500652
653 if args.version:
654 pv = args.version
655
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500656 if args.name:
657 pn = args.name
658 if args.name.endswith('-native'):
659 if args.also_native:
660 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')
661 sys.exit(1)
662 classes.append('native')
663 elif args.name.startswith('nativesdk-'):
664 if args.also_native:
665 logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
666 sys.exit(1)
667 classes.append('nativesdk')
668
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500669 if pv and pv not in 'git svn hg'.split():
670 realpv = pv
671 else:
672 realpv = None
673
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500674 if not srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500675 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
676 lines_before.append('SRC_URI = "%s"' % srcuri)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500677 for key, value in sorted(checksums.items()):
678 lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500679 if srcuri and supports_srcrev(srcuri):
680 lines_before.append('')
681 lines_before.append('# Modify these as desired')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500682 # Note: we have code to replace realpv further down if it gets set to some other value
683 scheme, _, _, _, _, _ = bb.fetch2.decodeurl(srcuri)
684 if scheme in ['git', 'gitsm']:
685 srcpvprefix = 'git'
686 elif scheme == 'svn':
687 srcpvprefix = 'svnr'
688 else:
689 srcpvprefix = scheme
690 lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix))
691 pv_srcpv = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600692 if not args.autorev and srcrev == '${AUTOREV}':
693 if os.path.exists(os.path.join(srctree, '.git')):
694 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Brad Bishopc342db32019-05-15 21:57:59 -0400695 srcrev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696 lines_before.append('SRCREV = "%s"' % srcrev)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500697 if args.provides:
698 lines_before.append('PROVIDES = "%s"' % args.provides)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500699 lines_before.append('')
700
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600701 if srcsubdir and not args.binary:
702 # (for binary packages we explicitly specify subdir= when fetching to
703 # match the default value of S, so we don't need to set it in that case)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500704 lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
705 lines_before.append('')
706
707 if pkgarch:
708 lines_after.append('PACKAGE_ARCH = "%s"' % pkgarch)
709 lines_after.append('')
710
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500711 if args.binary:
712 lines_after.append('INSANE_SKIP_${PN} += "already-stripped"')
713 lines_after.append('')
714
Andrew Geissler82c905d2020-04-13 13:39:40 -0500715 if args.npm_dev:
716 extravalues['NPM_INSTALL_DEV'] = 1
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500717
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500718 # Find all plugins that want to register handlers
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500719 logger.debug('Loading recipe handlers')
720 raw_handlers = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500721 for plugin in plugins:
722 if hasattr(plugin, 'register_recipe_handlers'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500723 plugin.register_recipe_handlers(raw_handlers)
724 # Sort handlers by priority
725 handlers = []
726 for i, handler in enumerate(raw_handlers):
727 if isinstance(handler, tuple):
728 handlers.append((handler[0], handler[1], i))
729 else:
730 handlers.append((handler, 0, i))
731 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
732 for handler, priority, _ in handlers:
733 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500734 setattr(handler, '_devtool', args.devtool)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500735 handlers = [item[0] for item in handlers]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500736
737 # Apply the handlers
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500738 if args.binary:
739 classes.append('bin_package')
740 handled.append('buildsystem')
741
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742 for handler in handlers:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500743 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
744
745 extrafiles = extravalues.pop('extrafiles', {})
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600746 extra_pn = extravalues.pop('PN', None)
747 extra_pv = extravalues.pop('PV', None)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500748
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600749 if extra_pv and not realpv:
750 realpv = extra_pv
751 if not validate_pv(realpv):
752 realpv = None
753 else:
754 realpv = realpv.lower().split()[0]
755 if '_' in realpv:
756 realpv = realpv.replace('_', '-')
757 if extra_pn and not pn:
758 pn = extra_pn
759 if pn.startswith('GNU '):
760 pn = pn[4:]
761 if ' ' in pn:
762 # Probably a descriptive identifier rather than a proper name
763 pn = None
764 else:
765 pn = pn.lower()
766 if '_' in pn:
767 pn = pn.replace('_', '-')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500768
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500769 if srcuri and not realpv or not pn:
770 name_pn, name_pv = determine_from_url(srcuri)
771 if name_pn and not pn:
772 pn = name_pn
773 if name_pv and not realpv:
774 realpv = name_pv
775
776 licvalues = handle_license_vars(srctree_use, lines_before, handled, extravalues, tinfoil.config_data)
777
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500778 if not outfile:
779 if not pn:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500780 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 -0500781 # devtool looks for this specific exit code, so don't change it
782 sys.exit(15)
783 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600784 if srcuri and srcuri.startswith(('gitsm://', 'git://', 'hg://', 'svn://')):
785 suffix = srcuri.split(':', 1)[0]
786 if suffix == 'gitsm':
787 suffix = 'git'
788 outfile = '%s_%s.bb' % (pn, suffix)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500789 elif realpv:
790 outfile = '%s_%s.bb' % (pn, realpv)
791 else:
792 outfile = '%s.bb' % pn
793 if outdir:
794 outfile = os.path.join(outdir, outfile)
795 # We need to check this again
796 if os.path.exists(outfile):
797 logger.error('Output file %s already exists' % outfile)
798 sys.exit(1)
799
800 # Move any extra files the plugins created to a directory next to the recipe
801 if extrafiles:
802 if outfile == '-':
803 extraoutdir = pn
804 else:
805 extraoutdir = os.path.join(os.path.dirname(outfile), pn)
806 bb.utils.mkdirhier(extraoutdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600807 for destfn, extrafile in extrafiles.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500808 shutil.move(extrafile, os.path.join(extraoutdir, destfn))
809
810 lines = lines_before
811 lines_before = []
812 skipblank = True
813 for line in lines:
814 if skipblank:
815 skipblank = False
816 if not line:
817 continue
818 if line.startswith('S = '):
819 if realpv and pv not in 'git svn hg'.split():
820 line = line.replace(realpv, '${PV}')
821 if pn:
822 line = line.replace(pn, '${BPN}')
823 if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
824 skipblank = True
825 continue
826 elif line.startswith('SRC_URI = '):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500827 if realpv and not pv_srcpv:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500828 line = line.replace(realpv, '${PV}')
829 elif line.startswith('PV = '):
830 if realpv:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500831 # Replace the first part of the PV value
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500832 line = re.sub('"[^+]*\+', '"%s+' % realpv, line)
833 lines_before.append(line)
834
835 if args.also_native:
836 lines = lines_after
837 lines_after = []
838 bbclassextend = None
839 for line in lines:
840 if line.startswith('BBCLASSEXTEND ='):
841 splitval = line.split('"')
842 if len(splitval) > 1:
843 bbclassextend = splitval[1].split()
844 if not 'native' in bbclassextend:
845 bbclassextend.insert(0, 'native')
846 line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
847 lines_after.append(line)
848 if not bbclassextend:
849 lines_after.append('BBCLASSEXTEND = "native"')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500850
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500851 postinst = ("postinst", extravalues.pop('postinst', None))
852 postrm = ("postrm", extravalues.pop('postrm', None))
853 preinst = ("preinst", extravalues.pop('preinst', None))
854 prerm = ("prerm", extravalues.pop('prerm', None))
855 funcs = [postinst, postrm, preinst, prerm]
856 for func in funcs:
857 if func[1]:
858 RecipeHandler.genfunction(lines_after, 'pkg_%s_${PN}' % func[0], func[1])
859
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500860 outlines = []
861 outlines.extend(lines_before)
862 if classes:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500863 if outlines[-1] and not outlines[-1].startswith('#'):
864 outlines.append('')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500865 outlines.append('inherit %s' % ' '.join(classes))
866 outlines.append('')
867 outlines.extend(lines_after)
868
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600869 if extravalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600870 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False)
871
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500872 if args.extract_to:
873 scriptutils.git_convert_standalone_clone(srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500874 if os.path.isdir(args.extract_to):
875 # If the directory exists we'll move the temp dir into it instead of
876 # its contents - of course, we could try to always move its contents
877 # but that is a pain if there are symlinks; the simplest solution is
878 # to just remove it first
879 os.rmdir(args.extract_to)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500880 shutil.move(srctree, args.extract_to)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500881 if tempsrc == srctree:
882 tempsrc = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500883 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500884
885 if outfile == '-':
886 sys.stdout.write('\n'.join(outlines) + '\n')
887 else:
888 with open(outfile, 'w') as f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600889 lastline = None
890 for line in outlines:
891 if not lastline and not line:
892 # Skip extra blank lines
893 continue
894 f.write('%s\n' % line)
895 lastline = line
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500896 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 -0500897
898 if tempsrc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600899 if args.keep_temp:
900 logger.info('Preserving temporary directory %s' % tempsrc)
901 else:
902 shutil.rmtree(tempsrc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500903
904 return 0
905
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500906def check_single_file(fn, fetchuri):
907 """Determine if a single downloaded file is something we can't handle"""
908 with open(fn, 'r', errors='surrogateescape') as f:
909 if '<html' in f.read(100).lower():
910 logger.error('Fetching "%s" returned a single HTML page - check the URL is correct and functional' % fetchuri)
911 sys.exit(1)
912
913def split_value(value):
914 if isinstance(value, str):
915 return value.split()
916 else:
917 return value
918
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600919def handle_license_vars(srctree, lines_before, handled, extravalues, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500920 lichandled = [x for x in handled if x[0] == 'license']
921 if lichandled:
922 # Someone else has already handled the license vars, just return their value
923 return lichandled[0][1]
924
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600925 licvalues = guess_license(srctree, d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500926 licenses = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600927 lic_files_chksum = []
928 lic_unknown = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500929 lines = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600930 if licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600931 for licvalue in licvalues:
932 if not licvalue[0] in licenses:
933 licenses.append(licvalue[0])
934 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
935 if licvalue[0] == 'Unknown':
936 lic_unknown.append(licvalue[1])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600937 if lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500938 lines.append('#')
939 lines.append('# The following license files were not able to be identified and are')
940 lines.append('# represented as "Unknown" below, you will need to check them yourself:')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600941 for licfile in lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500942 lines.append('# %s' % licfile)
943
944 extra_license = split_value(extravalues.pop('LICENSE', []))
945 if '&' in extra_license:
946 extra_license.remove('&')
947 if extra_license:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600948 if licenses == ['Unknown']:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500949 licenses = extra_license
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600950 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500951 for item in extra_license:
952 if item not in licenses:
953 licenses.append(item)
954 extra_lic_files_chksum = split_value(extravalues.pop('LIC_FILES_CHKSUM', []))
955 for item in extra_lic_files_chksum:
956 if item not in lic_files_chksum:
957 lic_files_chksum.append(item)
958
959 if lic_files_chksum:
960 # We are going to set the vars, so prepend the standard disclaimer
961 lines.insert(0, '# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is')
962 lines.insert(1, '# your responsibility to verify that the values are complete and correct.')
963 else:
964 # Without LIC_FILES_CHKSUM we set LICENSE = "CLOSED" to allow the
965 # user to get started easily
966 lines.append('# Unable to find any files that looked like license statements. Check the accompanying')
967 lines.append('# documentation and source headers and set LICENSE and LIC_FILES_CHKSUM accordingly.')
968 lines.append('#')
969 lines.append('# NOTE: LICENSE is being set to "CLOSED" to allow you to at least start building - if')
970 lines.append('# this is not accurate with respect to the licensing of the software being built (it')
971 lines.append('# will not be in most cases) you must specify the correct value before using this')
972 lines.append('# recipe for anything other than initial testing/development!')
973 licenses = ['CLOSED']
974
975 if extra_license and sorted(licenses) != sorted(extra_license):
976 lines.append('# NOTE: Original package / source metadata indicates license is: %s' % ' & '.join(extra_license))
977
978 if len(licenses) > 1:
979 lines.append('#')
980 lines.append('# NOTE: multiple licenses have been detected; they have been separated with &')
981 lines.append('# in the LICENSE value for now since it is a reasonable assumption that all')
982 lines.append('# of the licenses apply. If instead there is a choice between the multiple')
983 lines.append('# licenses then you should change the value to separate the licenses with |')
984 lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
985 lines.append('# to determine which situation is applicable.')
986
987 lines.append('LICENSE = "%s"' % ' & '.join(licenses))
988 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
989 lines.append('')
990
991 # Replace the placeholder so we get the values in the right place in the recipe file
992 try:
993 pos = lines_before.index('##LICENSE_PLACEHOLDER##')
994 except ValueError:
995 pos = -1
996 if pos == -1:
997 lines_before.extend(lines)
998 else:
999 lines_before[pos:pos+1] = lines
1000
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001001 handled.append(('license', licvalues))
1002 return licvalues
1003
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001004def get_license_md5sums(d, static_only=False):
1005 import bb.utils
1006 md5sums = {}
1007 if not static_only:
1008 # Gather md5sums of license files in common license dir
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001009 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001010 for fn in os.listdir(commonlicdir):
1011 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1012 md5sums[md5value] = fn
1013 # The following were extracted from common values in various recipes
1014 # (double checking the license against the license file itself, not just
1015 # the LICENSE value in the recipe)
1016 md5sums['94d55d512a9ba36caa9b7df079bae19f'] = 'GPLv2'
1017 md5sums['b234ee4d69f5fce4486a80fdaf4a4263'] = 'GPLv2'
1018 md5sums['59530bdf33659b29e73d4adb9f9f6552'] = 'GPLv2'
1019 md5sums['0636e73ff0215e8d672dc4c32c317bb3'] = 'GPLv2'
1020 md5sums['eb723b61539feef013de476e68b5c50a'] = 'GPLv2'
1021 md5sums['751419260aa954499f7abaabaa882bbe'] = 'GPLv2'
1022 md5sums['393a5ca445f6965873eca0259a17f833'] = 'GPLv2'
1023 md5sums['12f884d2ae1ff87c09e5b7ccc2c4ca7e'] = 'GPLv2'
1024 md5sums['8ca43cbc842c2336e835926c2166c28b'] = 'GPLv2'
1025 md5sums['ebb5c50ab7cab4baeffba14977030c07'] = 'GPLv2'
1026 md5sums['c93c0550bd3173f4504b2cbd8991e50b'] = 'GPLv2'
1027 md5sums['9ac2e7cff1ddaf48b6eab6028f23ef88'] = 'GPLv2'
1028 md5sums['4325afd396febcb659c36b49533135d4'] = 'GPLv2'
1029 md5sums['18810669f13b87348459e611d31ab760'] = 'GPLv2'
1030 md5sums['d7810fab7487fb0aad327b76f1be7cd7'] = 'GPLv2' # the Linux kernel's COPYING file
1031 md5sums['bbb461211a33b134d42ed5ee802b37ff'] = 'LGPLv2.1'
1032 md5sums['7fbc338309ac38fefcd64b04bb903e34'] = 'LGPLv2.1'
1033 md5sums['4fbd65380cdd255951079008b364516c'] = 'LGPLv2.1'
1034 md5sums['2d5025d4aa3495befef8f17206a5b0a1'] = 'LGPLv2.1'
1035 md5sums['fbc093901857fcd118f065f900982c24'] = 'LGPLv2.1'
1036 md5sums['a6f89e2100d9b6cdffcea4f398e37343'] = 'LGPLv2.1'
1037 md5sums['d8045f3b8f929c1cb29a1e3fd737b499'] = 'LGPLv2.1'
1038 md5sums['fad9b3332be894bab9bc501572864b29'] = 'LGPLv2.1'
1039 md5sums['3bf50002aefd002f49e7bb854063f7e7'] = 'LGPLv2'
1040 md5sums['9f604d8a4f8e74f4f5140845a21b6674'] = 'LGPLv2'
1041 md5sums['5f30f0716dfdd0d91eb439ebec522ec2'] = 'LGPLv2'
1042 md5sums['55ca817ccb7d5b5b66355690e9abc605'] = 'LGPLv2'
1043 md5sums['252890d9eee26aab7b432e8b8a616475'] = 'LGPLv2'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001044 md5sums['3214f080875748938ba060314b4f727d'] = 'LGPLv2'
1045 md5sums['db979804f025cf55aabec7129cb671ed'] = 'LGPLv2'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001046 md5sums['d32239bcb673463ab874e80d47fae504'] = 'GPLv3'
1047 md5sums['f27defe1e96c2e1ecd4e0c9be8967949'] = 'GPLv3'
1048 md5sums['6a6a8e020838b23406c81b19c1d46df6'] = 'LGPLv3'
1049 md5sums['3b83ef96387f14655fc854ddc3c6bd57'] = 'Apache-2.0'
1050 md5sums['385c55653886acac3821999a3ccd17b3'] = 'Artistic-1.0 | GPL-2.0' # some perl modules
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001051 md5sums['54c7042be62e169199200bc6477f04d1'] = 'BSD-3-Clause'
Brad Bishop96ff1982019-08-19 13:50:42 -04001052 md5sums['bfe1f75d606912a4111c90743d6c7325'] = 'MPL-1.1'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001053 return md5sums
1054
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001055def crunch_license(licfile):
1056 '''
1057 Remove non-material text from a license file and then check
1058 its md5sum against a known list. This works well for licenses
1059 which contain a copyright statement, but is also a useful way
1060 to handle people's insistence upon reformatting the license text
1061 slightly (with no material difference to the text of the
1062 license).
1063 '''
1064
1065 import oe.utils
1066
1067 # Note: these are carefully constructed!
1068 license_title_re = re.compile('^\(?(#+ *)?(The )?.{1,10} [Ll]icen[sc]e( \(.{1,10}\))?\)?:?$')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001069 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 -05001070 copyright_re = re.compile('^(#+)? *Copyright .*$')
1071
1072 crunched_md5sums = {}
1073 # The following two were gleaned from the "forever" npm package
1074 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
1075 crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
1076 # https://github.com/vasi/pixz/blob/master/LICENSE
1077 crunched_md5sums['2f03392b40bbe663597b5bd3cc5ebdb9'] = 'BSD-2-Clause'
1078 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
1079 crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause'
1080 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
1081 crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2'
1082 # https://github.com/datto/dattobd/blob/master/COPYING
1083 # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT
1084 crunched_md5sums['1d65c5ad4bf6489f85f4812bf08ae73d'] = 'GPLv2'
1085 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
1086 # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD
1087 crunched_md5sums['fb530f66a7a89ce920f0e912b5b66d4b'] = 'GPLv2'
1088 # https://github.com/gkos/nrf24/blob/master/COPYING
1089 crunched_md5sums['7b6aaa4daeafdfa6ed5443fd2684581b'] = 'GPLv2'
1090 # https://github.com/josch09/resetusb/blob/master/COPYING
1091 crunched_md5sums['8b8ac1d631a4d220342e83bcf1a1fbc3'] = 'GPLv3'
1092 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
1093 crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1'
1094 # unixODBC-2.3.4 COPYING
1095 crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1'
1096 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
1097 crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3'
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001098 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1099 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
1100 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/edl-v10
1101 crunched_md5sums['0a9c78c0a398d1bbce4a166757d60387'] = 'EDL-1.0'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001102 lictext = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001103 with open(licfile, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001104 for line in f:
1105 # Drop opening statements
1106 if copyright_re.match(line):
1107 continue
1108 elif license_title_re.match(line):
1109 continue
1110 elif license_statement_re.match(line):
1111 continue
1112 # Squash spaces, and replace smart quotes, double quotes
1113 # and backticks with single quotes
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001114 line = oe.utils.squashspaces(line.strip())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001115 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
1116 if line:
1117 lictext.append(line)
1118
1119 m = hashlib.md5()
1120 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001121 m.update(' '.join(lictext).encode('utf-8'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001122 md5val = m.hexdigest()
1123 except UnicodeEncodeError:
1124 md5val = None
1125 lictext = ''
1126 license = crunched_md5sums.get(md5val, None)
1127 return license, md5val, lictext
1128
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001129def guess_license(srctree, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001130 import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001131 md5sums = get_license_md5sums(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001132
1133 licenses = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001134 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 -05001135 licfiles = []
1136 for root, dirs, files in os.walk(srctree):
1137 for fn in files:
1138 for spec in licspecs:
1139 if fnmatch.fnmatch(fn, spec):
1140 fullpath = os.path.join(root, fn)
1141 if not fullpath in licfiles:
1142 licfiles.append(fullpath)
1143 for licfile in licfiles:
1144 md5value = bb.utils.md5_file(licfile)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001145 license = md5sums.get(md5value, None)
1146 if not license:
1147 license, crunched_md5, lictext = crunch_license(licfile)
1148 if not license:
1149 license = 'Unknown'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001150 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
1151
1152 # FIXME should we grab at least one source file with a license header and add that too?
1153
1154 return licenses
1155
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001156def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
1157 """
1158 Given a list of (license, path, md5sum) as returned by guess_license(),
1159 a dict of package name to path mappings, write out a set of
1160 package-specific LICENSE values.
1161 """
1162 pkglicenses = {pn: []}
1163 for license, licpath, _ in licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001164 for pkgname, pkgpath in packages.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001165 if licpath.startswith(pkgpath + '/'):
1166 if pkgname in pkglicenses:
1167 pkglicenses[pkgname].append(license)
1168 else:
1169 pkglicenses[pkgname] = [license]
1170 break
1171 else:
1172 # Accumulate on the main package
1173 pkglicenses[pn].append(license)
1174 outlicenses = {}
1175 for pkgname in packages:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001176 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001177 if license == 'Unknown' and pkgname in fallback_licenses:
1178 license = fallback_licenses[pkgname]
1179 outlines.append('LICENSE_%s = "%s"' % (pkgname, license))
1180 outlicenses[pkgname] = license.split()
1181 return outlicenses
1182
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001183def read_pkgconfig_provides(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001184 pkgdatadir = d.getVar('PKGDATA_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001185 pkgmap = {}
1186 for fn in glob.glob(os.path.join(pkgdatadir, 'shlibs2', '*.pclist')):
1187 with open(fn, 'r') as f:
1188 for line in f:
1189 pkgmap[os.path.basename(line.rstrip())] = os.path.splitext(os.path.basename(fn))[0]
1190 recipemap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001191 for pc, pkg in pkgmap.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001192 pkgdatafile = os.path.join(pkgdatadir, 'runtime', pkg)
1193 if os.path.exists(pkgdatafile):
1194 with open(pkgdatafile, 'r') as f:
1195 for line in f:
1196 if line.startswith('PN: '):
1197 recipemap[pc] = line.split(':', 1)[1].strip()
1198 return recipemap
1199
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001200def convert_debian(debpath):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001201 value_map = {'Package': 'PN',
1202 'Version': 'PV',
1203 'Section': 'SECTION',
1204 'License': 'LICENSE',
1205 'Homepage': 'HOMEPAGE'}
1206
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001207 # FIXME extend this mapping - perhaps use distro_alias.inc?
1208 depmap = {'libz-dev': 'zlib'}
1209
1210 values = {}
1211 depends = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001212 with open(os.path.join(debpath, 'control'), 'r', errors='surrogateescape') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001213 indesc = False
1214 for line in f:
1215 if indesc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001216 if line.startswith(' '):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001217 if line.startswith(' This package contains'):
1218 indesc = False
1219 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001220 if 'DESCRIPTION' in values:
1221 values['DESCRIPTION'] += ' ' + line.strip()
1222 else:
1223 values['DESCRIPTION'] = line.strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001224 else:
1225 indesc = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001226 if not indesc:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001227 splitline = line.split(':', 1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001228 if len(splitline) < 2:
1229 continue
1230 key = splitline[0]
1231 value = splitline[1].strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001232 if key == 'Build-Depends':
1233 for dep in value.split(','):
1234 dep = dep.split()[0]
1235 mapped = depmap.get(dep, '')
1236 if mapped:
1237 depends.append(mapped)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001238 elif key == 'Description':
1239 values['SUMMARY'] = value
1240 indesc = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001241 else:
1242 varname = value_map.get(key, None)
1243 if varname:
1244 values[varname] = value
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001245 postinst = os.path.join(debpath, 'postinst')
1246 postrm = os.path.join(debpath, 'postrm')
1247 preinst = os.path.join(debpath, 'preinst')
1248 prerm = os.path.join(debpath, 'prerm')
1249 sfiles = [postinst, postrm, preinst, prerm]
1250 for sfile in sfiles:
1251 if os.path.isfile(sfile):
1252 logger.info("Converting %s file to recipe function..." %
1253 os.path.basename(sfile).upper())
1254 content = []
1255 with open(sfile) as f:
1256 for line in f:
1257 if "#!/" in line:
1258 continue
1259 line = line.rstrip("\n")
1260 if line.strip():
1261 content.append(line)
1262 if content:
1263 values[os.path.basename(f.name)] = content
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001264
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001265 #if depends:
1266 # values['DEPENDS'] = ' '.join(depends)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001267
1268 return values
1269
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001270def convert_rpm_xml(xmlfile):
1271 '''Converts the output from rpm -qp --xml to a set of variable values'''
1272 import xml.etree.ElementTree as ElementTree
1273 rpmtag_map = {'Name': 'PN',
1274 'Version': 'PV',
1275 'Summary': 'SUMMARY',
1276 'Description': 'DESCRIPTION',
1277 'License': 'LICENSE',
1278 'Url': 'HOMEPAGE'}
1279
1280 values = {}
1281 tree = ElementTree.parse(xmlfile)
1282 root = tree.getroot()
1283 for child in root:
1284 if child.tag == 'rpmTag':
1285 name = child.attrib.get('name', None)
1286 if name:
1287 varname = rpmtag_map.get(name, None)
1288 if varname:
1289 values[varname] = child[0].text
1290 return values
1291
1292
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001293def register_commands(subparsers):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001294 parser_create = subparsers.add_parser('create',
1295 help='Create a new recipe',
1296 description='Creates a new recipe from a source tree')
1297 parser_create.add_argument('source', help='Path or URL to source')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001298 parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001299 parser_create.add_argument('-p', '--provides', help='Specify an alias for the item provided by the recipe')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001300 parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
1301 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 -05001302 parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001303 parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001304 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 -05001305 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')
1306 parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001307 group = parser_create.add_mutually_exclusive_group()
1308 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")
1309 group.add_argument('-S', '--srcrev', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
1310 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 -06001311 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Andrew Geissler82c905d2020-04-13 13:39:40 -05001312 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies')
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001313 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001314 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1315 parser_create.set_defaults(func=create_recipe)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001316