blob: 5a267fb57c6497e0f2285124d16002ac3336d41b [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]))
Andrew Geisslerb7d28612020-07-24 16:15:54 -050069 libname_re = re.compile(r'^lib(.+)\.so.*$')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050070 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()
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500118 elif line.startswith('FILES_INFO:%s:' % pkg):
119 val = line.split(': ', 1)[1].strip()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500120 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
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500431 if re.match(r'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
Andrew Geisslerd25ed322020-06-27 00:28:28 -0500463 params['branch'] = srcbranch
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500464 nobranch = params.get('nobranch')
465 if nobranch and srcbranch:
466 logger.error('nobranch= cannot be used if you specify a branch')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500467 sys.exit(1)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500468 tag = params.get('tag')
469 if not srcbranch and not nobranch and srcrev != '${AUTOREV}':
470 # Append nobranch=1 in the following conditions:
471 # 1. User did not set 'branch=' in srcuri, and
472 # 2. User did not set 'nobranch=1' in srcuri, and
473 # 3. Source revision is not '${AUTOREV}'
474 params['nobranch'] = '1'
475 if tag:
476 # Keep a copy of tag and append nobranch=1 then remove tag from URL.
477 # Bitbake fetcher unable to fetch when {AUTOREV} and tag is set at the same time.
478 storeTagName = params['tag']
479 params['nobranch'] = '1'
480 del params['tag']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500481 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
482
483 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
484 bb.utils.mkdirhier(tmpparent)
485 tempsrc = tempfile.mkdtemp(prefix='recipetool-', dir=tmpparent)
486 srctree = os.path.join(tempsrc, 'source')
487
488 try:
489 checksums, ftmpdir = scriptutils.fetch_url(tinfoil, fetchuri, srcrev, srctree, logger, preserve_tmp=args.keep_temp)
490 except scriptutils.FetchUrlFailure as e:
491 logger.error(str(e))
492 sys.exit(1)
493
494 if ftmpdir and args.keep_temp:
495 logger.info('Fetch temp directory is %s' % ftmpdir)
496
Brad Bishop6dbb3162019-11-25 09:41:34 -0500497 dirlist = scriptutils.filter_src_subdirs(srctree)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500498 logger.debug('Directory listing (excluding filtered out):\n %s' % '\n '.join(dirlist))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500499 if len(dirlist) == 1:
500 singleitem = os.path.join(srctree, dirlist[0])
501 if os.path.isdir(singleitem):
502 # We unpacked a single directory, so we should use that
503 srcsubdir = dirlist[0]
504 srctree = os.path.join(srctree, srcsubdir)
505 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500506 check_single_file(dirlist[0], fetchuri)
507 elif len(dirlist) == 0:
508 if '/' in fetchuri:
509 fn = os.path.join(tinfoil.config_data.getVar('DL_DIR'), fetchuri.split('/')[-1])
510 if os.path.isfile(fn):
511 check_single_file(fn, fetchuri)
512 # If we've got to here then there's no source so we might as well give up
513 logger.error('URL %s resulted in an empty source tree' % fetchuri)
514 sys.exit(1)
515
516 # We need this checking mechanism to improve the recipe created by recipetool and devtool
517 # is able to parse and build by bitbake.
518 # If there is no input for branch name, then check for branch name with SRCREV provided.
519 if not srcbranch and not nobranch and srcrev and (srcrev != '${AUTOREV}') and scheme in ['git', 'gitsm']:
520 try:
521 cmd = 'git branch -r --contains'
522 check_branch, check_branch_err = bb.process.run('%s %s' % (cmd, srcrev), cwd=srctree)
523 except bb.process.ExecutionError as err:
524 logger.error(str(err))
525 sys.exit(1)
526 get_branch = [x.strip() for x in check_branch.splitlines()]
527 # Remove HEAD reference point and drop remote prefix
528 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
529 if 'master' in get_branch:
530 # If it is master, we do not need to append 'branch=master' as this is default.
531 # Even with the case where get_branch has multiple objects, if 'master' is one
532 # of them, we should default take from 'master'
533 srcbranch = ''
534 elif len(get_branch) == 1:
535 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
536 srcbranch = get_branch[0]
537 else:
538 # If get_branch contains more than one objects, then display error and exit.
539 mbrch = '\n ' + '\n '.join(get_branch)
540 logger.error('Revision %s was found on multiple branches: %s\nPlease provide the correct branch with -B/--srcbranch' % (srcrev, mbrch))
541 sys.exit(1)
542
543 # Since we might have a value in srcbranch, we need to
544 # recontruct the srcuri to include 'branch' in params.
545 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
546 if srcbranch:
547 params['branch'] = srcbranch
548
549 if storeTagName and scheme in ['git', 'gitsm']:
550 # Check srcrev using tag and check validity of the tag
551 cmd = ('git rev-parse --verify %s' % (storeTagName))
552 try:
553 check_tag, check_tag_err = bb.process.run('%s' % cmd, cwd=srctree)
554 srcrev = check_tag.split()[0]
555 except bb.process.ExecutionError as err:
556 logger.error(str(err))
557 logger.error("Possibly wrong tag name is provided")
558 sys.exit(1)
559 # Drop tag from srcuri as it will have conflicts with SRCREV during recipe parse.
560 del params['tag']
561 srcuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
562
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600563 if os.path.exists(os.path.join(srctree, '.gitmodules')) and srcuri.startswith('git://'):
564 srcuri = 'gitsm://' + srcuri[6:]
565 logger.info('Fetching submodules...')
566 bb.process.run('git submodule update --init --recursive', cwd=srctree)
567
568 if is_package(fetchuri):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500569 localdata = bb.data.createCopy(tinfoil.config_data)
570 pkgfile = bb.fetch2.localpath(fetchuri, localdata)
571 if pkgfile:
572 tmpfdir = tempfile.mkdtemp(prefix='recipetool-')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600573 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600574 if pkgfile.endswith(('.deb', '.ipk')):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500575 stdout, _ = bb.process.run('ar x %s' % pkgfile, cwd=tmpfdir)
576 stdout, _ = bb.process.run('tar xf control.tar.gz', cwd=tmpfdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600577 values = convert_debian(tmpfdir)
578 extravalues.update(values)
579 elif pkgfile.endswith(('.rpm', '.srpm')):
580 stdout, _ = bb.process.run('rpm -qp --xml %s > pkginfo.xml' % pkgfile, cwd=tmpfdir)
581 values = convert_rpm_xml(os.path.join(tmpfdir, 'pkginfo.xml'))
582 extravalues.update(values)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500583 finally:
584 shutil.rmtree(tmpfdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585 else:
586 # Assume we're pointing to an existing source tree
587 if args.extract_to:
588 logger.error('--extract-to cannot be specified if source is a directory')
589 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600590 if not os.path.isdir(source):
591 logger.error('Invalid source directory %s' % source)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600593 srctree = source
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500594 srcuri = ''
595 if os.path.exists(os.path.join(srctree, '.git')):
596 # Try to get upstream repo location from origin remote
597 try:
598 stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
599 except bb.process.ExecutionError as e:
600 stdout = None
601 if stdout:
602 for line in stdout.splitlines():
603 splitline = line.split()
604 if len(splitline) > 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600605 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500606 srcuri = reformat_git_uri(splitline[1])
607 srcsubdir = 'git'
608 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500609
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500610 if args.src_subdir:
611 srcsubdir = os.path.join(srcsubdir, args.src_subdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500612 srctree_use = os.path.abspath(os.path.join(srctree, args.src_subdir))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500613 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500614 srctree_use = os.path.abspath(srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500615
616 if args.outfile and os.path.isdir(args.outfile):
617 outfile = None
618 outdir = args.outfile
619 else:
620 outfile = args.outfile
621 outdir = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500622 if outfile and outfile != '-':
623 if os.path.exists(outfile):
624 logger.error('Output file %s already exists' % outfile)
625 sys.exit(1)
626
627 lines_before = []
628 lines_after = []
629
630 lines_before.append('# Recipe created by %s' % os.path.basename(sys.argv[0]))
631 lines_before.append('# This is the basis of a recipe and may need further editing in order to be fully functional.')
632 lines_before.append('# (Feel free to remove these comments when editing.)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600633 # We need a blank line here so that patch_recipe_lines can rewind before the LICENSE comments
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500634 lines_before.append('')
635
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500636 # We'll come back and replace this later in handle_license_vars()
637 lines_before.append('##LICENSE_PLACEHOLDER##')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600638
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500639 handled = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500640 classes = []
641
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500642 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500643 pn = None
644 pv = None
645 if outfile:
646 recipefn = os.path.splitext(os.path.basename(outfile))[0]
647 fnsplit = recipefn.split('_')
648 if len(fnsplit) > 1:
649 pn = fnsplit[0]
650 pv = fnsplit[1]
651 else:
652 pn = recipefn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500653
654 if args.version:
655 pv = args.version
656
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500657 if args.name:
658 pn = args.name
659 if args.name.endswith('-native'):
660 if args.also_native:
661 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')
662 sys.exit(1)
663 classes.append('native')
664 elif args.name.startswith('nativesdk-'):
665 if args.also_native:
666 logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
667 sys.exit(1)
668 classes.append('nativesdk')
669
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500670 if pv and pv not in 'git svn hg'.split():
671 realpv = pv
672 else:
673 realpv = None
674
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500675 if not srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500676 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
677 lines_before.append('SRC_URI = "%s"' % srcuri)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500678 for key, value in sorted(checksums.items()):
679 lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500680 if srcuri and supports_srcrev(srcuri):
681 lines_before.append('')
682 lines_before.append('# Modify these as desired')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500683 # Note: we have code to replace realpv further down if it gets set to some other value
684 scheme, _, _, _, _, _ = bb.fetch2.decodeurl(srcuri)
685 if scheme in ['git', 'gitsm']:
686 srcpvprefix = 'git'
687 elif scheme == 'svn':
688 srcpvprefix = 'svnr'
689 else:
690 srcpvprefix = scheme
691 lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix))
692 pv_srcpv = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600693 if not args.autorev and srcrev == '${AUTOREV}':
694 if os.path.exists(os.path.join(srctree, '.git')):
695 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Brad Bishopc342db32019-05-15 21:57:59 -0400696 srcrev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500697 lines_before.append('SRCREV = "%s"' % srcrev)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500698 if args.provides:
699 lines_before.append('PROVIDES = "%s"' % args.provides)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500700 lines_before.append('')
701
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600702 if srcsubdir and not args.binary:
703 # (for binary packages we explicitly specify subdir= when fetching to
704 # match the default value of S, so we don't need to set it in that case)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500705 lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
706 lines_before.append('')
707
708 if pkgarch:
709 lines_after.append('PACKAGE_ARCH = "%s"' % pkgarch)
710 lines_after.append('')
711
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500712 if args.binary:
Patrick Williams213cb262021-08-07 19:21:33 -0500713 lines_after.append('INSANE_SKIP:${PN} += "already-stripped"')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500714 lines_after.append('')
715
Andrew Geissler82c905d2020-04-13 13:39:40 -0500716 if args.npm_dev:
717 extravalues['NPM_INSTALL_DEV'] = 1
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500718
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500719 # Find all plugins that want to register handlers
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500720 logger.debug('Loading recipe handlers')
721 raw_handlers = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500722 for plugin in plugins:
723 if hasattr(plugin, 'register_recipe_handlers'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500724 plugin.register_recipe_handlers(raw_handlers)
725 # Sort handlers by priority
726 handlers = []
727 for i, handler in enumerate(raw_handlers):
728 if isinstance(handler, tuple):
729 handlers.append((handler[0], handler[1], i))
730 else:
731 handlers.append((handler, 0, i))
732 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
733 for handler, priority, _ in handlers:
734 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500735 setattr(handler, '_devtool', args.devtool)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500736 handlers = [item[0] for item in handlers]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500737
738 # Apply the handlers
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500739 if args.binary:
740 classes.append('bin_package')
741 handled.append('buildsystem')
742
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500743 for handler in handlers:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500744 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
745
746 extrafiles = extravalues.pop('extrafiles', {})
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600747 extra_pn = extravalues.pop('PN', None)
748 extra_pv = extravalues.pop('PV', None)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500749
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600750 if extra_pv and not realpv:
751 realpv = extra_pv
752 if not validate_pv(realpv):
753 realpv = None
754 else:
755 realpv = realpv.lower().split()[0]
756 if '_' in realpv:
757 realpv = realpv.replace('_', '-')
758 if extra_pn and not pn:
759 pn = extra_pn
760 if pn.startswith('GNU '):
761 pn = pn[4:]
762 if ' ' in pn:
763 # Probably a descriptive identifier rather than a proper name
764 pn = None
765 else:
766 pn = pn.lower()
767 if '_' in pn:
768 pn = pn.replace('_', '-')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500769
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500770 if srcuri and not realpv or not pn:
771 name_pn, name_pv = determine_from_url(srcuri)
772 if name_pn and not pn:
773 pn = name_pn
774 if name_pv and not realpv:
775 realpv = name_pv
776
777 licvalues = handle_license_vars(srctree_use, lines_before, handled, extravalues, tinfoil.config_data)
778
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500779 if not outfile:
780 if not pn:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500781 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 -0500782 # devtool looks for this specific exit code, so don't change it
783 sys.exit(15)
784 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600785 if srcuri and srcuri.startswith(('gitsm://', 'git://', 'hg://', 'svn://')):
786 suffix = srcuri.split(':', 1)[0]
787 if suffix == 'gitsm':
788 suffix = 'git'
789 outfile = '%s_%s.bb' % (pn, suffix)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500790 elif realpv:
791 outfile = '%s_%s.bb' % (pn, realpv)
792 else:
793 outfile = '%s.bb' % pn
794 if outdir:
795 outfile = os.path.join(outdir, outfile)
796 # We need to check this again
797 if os.path.exists(outfile):
798 logger.error('Output file %s already exists' % outfile)
799 sys.exit(1)
800
801 # Move any extra files the plugins created to a directory next to the recipe
802 if extrafiles:
803 if outfile == '-':
804 extraoutdir = pn
805 else:
806 extraoutdir = os.path.join(os.path.dirname(outfile), pn)
807 bb.utils.mkdirhier(extraoutdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600808 for destfn, extrafile in extrafiles.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500809 shutil.move(extrafile, os.path.join(extraoutdir, destfn))
810
811 lines = lines_before
812 lines_before = []
813 skipblank = True
814 for line in lines:
815 if skipblank:
816 skipblank = False
817 if not line:
818 continue
819 if line.startswith('S = '):
820 if realpv and pv not in 'git svn hg'.split():
821 line = line.replace(realpv, '${PV}')
822 if pn:
823 line = line.replace(pn, '${BPN}')
824 if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
825 skipblank = True
826 continue
827 elif line.startswith('SRC_URI = '):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500828 if realpv and not pv_srcpv:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500829 line = line.replace(realpv, '${PV}')
830 elif line.startswith('PV = '):
831 if realpv:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500832 # Replace the first part of the PV value
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500833 line = re.sub(r'"[^+]*\+', '"%s+' % realpv, line)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500834 lines_before.append(line)
835
836 if args.also_native:
837 lines = lines_after
838 lines_after = []
839 bbclassextend = None
840 for line in lines:
841 if line.startswith('BBCLASSEXTEND ='):
842 splitval = line.split('"')
843 if len(splitval) > 1:
844 bbclassextend = splitval[1].split()
845 if not 'native' in bbclassextend:
846 bbclassextend.insert(0, 'native')
847 line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
848 lines_after.append(line)
849 if not bbclassextend:
850 lines_after.append('BBCLASSEXTEND = "native"')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500851
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500852 postinst = ("postinst", extravalues.pop('postinst', None))
853 postrm = ("postrm", extravalues.pop('postrm', None))
854 preinst = ("preinst", extravalues.pop('preinst', None))
855 prerm = ("prerm", extravalues.pop('prerm', None))
856 funcs = [postinst, postrm, preinst, prerm]
857 for func in funcs:
858 if func[1]:
859 RecipeHandler.genfunction(lines_after, 'pkg_%s_${PN}' % func[0], func[1])
860
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500861 outlines = []
862 outlines.extend(lines_before)
863 if classes:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500864 if outlines[-1] and not outlines[-1].startswith('#'):
865 outlines.append('')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500866 outlines.append('inherit %s' % ' '.join(classes))
867 outlines.append('')
868 outlines.extend(lines_after)
869
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600870 if extravalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600871 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False)
872
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500873 if args.extract_to:
874 scriptutils.git_convert_standalone_clone(srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500875 if os.path.isdir(args.extract_to):
876 # If the directory exists we'll move the temp dir into it instead of
877 # its contents - of course, we could try to always move its contents
878 # but that is a pain if there are symlinks; the simplest solution is
879 # to just remove it first
880 os.rmdir(args.extract_to)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500881 shutil.move(srctree, args.extract_to)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500882 if tempsrc == srctree:
883 tempsrc = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500884 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500885
886 if outfile == '-':
887 sys.stdout.write('\n'.join(outlines) + '\n')
888 else:
889 with open(outfile, 'w') as f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600890 lastline = None
891 for line in outlines:
892 if not lastline and not line:
893 # Skip extra blank lines
894 continue
895 f.write('%s\n' % line)
896 lastline = line
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500897 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 -0500898
899 if tempsrc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600900 if args.keep_temp:
901 logger.info('Preserving temporary directory %s' % tempsrc)
902 else:
903 shutil.rmtree(tempsrc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500904
905 return 0
906
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500907def check_single_file(fn, fetchuri):
908 """Determine if a single downloaded file is something we can't handle"""
909 with open(fn, 'r', errors='surrogateescape') as f:
910 if '<html' in f.read(100).lower():
911 logger.error('Fetching "%s" returned a single HTML page - check the URL is correct and functional' % fetchuri)
912 sys.exit(1)
913
914def split_value(value):
915 if isinstance(value, str):
916 return value.split()
917 else:
918 return value
919
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600920def handle_license_vars(srctree, lines_before, handled, extravalues, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500921 lichandled = [x for x in handled if x[0] == 'license']
922 if lichandled:
923 # Someone else has already handled the license vars, just return their value
924 return lichandled[0][1]
925
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600926 licvalues = guess_license(srctree, d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500927 licenses = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600928 lic_files_chksum = []
929 lic_unknown = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500930 lines = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600931 if licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600932 for licvalue in licvalues:
933 if not licvalue[0] in licenses:
934 licenses.append(licvalue[0])
935 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
936 if licvalue[0] == 'Unknown':
937 lic_unknown.append(licvalue[1])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600938 if lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500939 lines.append('#')
940 lines.append('# The following license files were not able to be identified and are')
941 lines.append('# represented as "Unknown" below, you will need to check them yourself:')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600942 for licfile in lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500943 lines.append('# %s' % licfile)
944
945 extra_license = split_value(extravalues.pop('LICENSE', []))
946 if '&' in extra_license:
947 extra_license.remove('&')
948 if extra_license:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600949 if licenses == ['Unknown']:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500950 licenses = extra_license
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600951 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500952 for item in extra_license:
953 if item not in licenses:
954 licenses.append(item)
955 extra_lic_files_chksum = split_value(extravalues.pop('LIC_FILES_CHKSUM', []))
956 for item in extra_lic_files_chksum:
957 if item not in lic_files_chksum:
958 lic_files_chksum.append(item)
959
960 if lic_files_chksum:
961 # We are going to set the vars, so prepend the standard disclaimer
962 lines.insert(0, '# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is')
963 lines.insert(1, '# your responsibility to verify that the values are complete and correct.')
964 else:
965 # Without LIC_FILES_CHKSUM we set LICENSE = "CLOSED" to allow the
966 # user to get started easily
967 lines.append('# Unable to find any files that looked like license statements. Check the accompanying')
968 lines.append('# documentation and source headers and set LICENSE and LIC_FILES_CHKSUM accordingly.')
969 lines.append('#')
970 lines.append('# NOTE: LICENSE is being set to "CLOSED" to allow you to at least start building - if')
971 lines.append('# this is not accurate with respect to the licensing of the software being built (it')
972 lines.append('# will not be in most cases) you must specify the correct value before using this')
973 lines.append('# recipe for anything other than initial testing/development!')
974 licenses = ['CLOSED']
975
976 if extra_license and sorted(licenses) != sorted(extra_license):
977 lines.append('# NOTE: Original package / source metadata indicates license is: %s' % ' & '.join(extra_license))
978
979 if len(licenses) > 1:
980 lines.append('#')
981 lines.append('# NOTE: multiple licenses have been detected; they have been separated with &')
982 lines.append('# in the LICENSE value for now since it is a reasonable assumption that all')
983 lines.append('# of the licenses apply. If instead there is a choice between the multiple')
984 lines.append('# licenses then you should change the value to separate the licenses with |')
985 lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
986 lines.append('# to determine which situation is applicable.')
987
988 lines.append('LICENSE = "%s"' % ' & '.join(licenses))
989 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
990 lines.append('')
991
992 # Replace the placeholder so we get the values in the right place in the recipe file
993 try:
994 pos = lines_before.index('##LICENSE_PLACEHOLDER##')
995 except ValueError:
996 pos = -1
997 if pos == -1:
998 lines_before.extend(lines)
999 else:
1000 lines_before[pos:pos+1] = lines
1001
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001002 handled.append(('license', licvalues))
1003 return licvalues
1004
Andrew Geisslereff27472021-10-29 15:35:00 -05001005def get_license_md5sums(d, static_only=False, linenumbers=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001006 import bb.utils
Andrew Geisslereff27472021-10-29 15:35:00 -05001007 import csv
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001008 md5sums = {}
Andrew Geisslereff27472021-10-29 15:35:00 -05001009 if not static_only and not linenumbers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001010 # Gather md5sums of license files in common license dir
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001011 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001012 for fn in os.listdir(commonlicdir):
1013 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1014 md5sums[md5value] = fn
Andrew Geisslereff27472021-10-29 15:35:00 -05001015
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001016 # The following were extracted from common values in various recipes
1017 # (double checking the license against the license file itself, not just
1018 # the LICENSE value in the recipe)
Andrew Geisslereff27472021-10-29 15:35:00 -05001019
1020 # Read license md5sums from csv file
1021 scripts_path = os.path.dirname(os.path.realpath(__file__))
1022 for path in (d.getVar('BBPATH').split(':')
1023 + [os.path.join(scripts_path, '..', '..')]):
1024 csv_path = os.path.join(path, 'lib', 'recipetool', 'licenses.csv')
1025 if os.path.isfile(csv_path):
1026 with open(csv_path, newline='') as csv_file:
1027 fieldnames = ['md5sum', 'license', 'beginline', 'endline', 'md5']
1028 reader = csv.DictReader(csv_file, delimiter=',', fieldnames=fieldnames)
1029 for row in reader:
1030 if linenumbers:
1031 md5sums[row['md5sum']] = (
1032 row['license'], row['beginline'], row['endline'], row['md5'])
1033 else:
1034 md5sums[row['md5sum']] = row['license']
1035
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001036 return md5sums
1037
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001038def crunch_license(licfile):
1039 '''
1040 Remove non-material text from a license file and then check
1041 its md5sum against a known list. This works well for licenses
1042 which contain a copyright statement, but is also a useful way
1043 to handle people's insistence upon reformatting the license text
1044 slightly (with no material difference to the text of the
1045 license).
1046 '''
1047
1048 import oe.utils
1049
1050 # Note: these are carefully constructed!
Andrew Geisslereff27472021-10-29 15:35:00 -05001051 license_title_re = re.compile(r'^#*\(? *(This is )?([Tt]he )?.{0,15} ?[Ll]icen[sc]e( \(.{1,10}\))?\)?[:\.]? ?#*$')
1052 license_statement_re = re.compile(r'^((This (project|software)|.{1,10}) is( free software)? (released|licen[sc]ed)|(Released|Licen[cs]ed)) under the .{1,10} [Ll]icen[sc]e:?$')
1053 copyright_re = re.compile('^ *[#\*]* *(Modified work |MIT LICENSED )?Copyright ?(\([cC]\))? .*$')
1054 disclaimer_re = re.compile('^ *\*? ?All [Rr]ights [Rr]eserved\.$')
1055 email_re = re.compile('^.*<[\w\.-]*@[\w\.\-]*>$')
1056 header_re = re.compile('^(\/\**!?)? ?[\-=\*]* ?(\*\/)?$')
1057 tag_re = re.compile('^ *@?\(?([Ll]icense|MIT)\)?$')
1058 url_re = re.compile('^ *[#\*]* *https?:\/\/[\w\.\/\-]+$')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001059
1060 crunched_md5sums = {}
Andrew Geisslereff27472021-10-29 15:35:00 -05001061
1062 # common licenses
1063 crunched_md5sums['89f3bf322f30a1dcfe952e09945842f0'] = 'Apache-2.0'
1064 crunched_md5sums['13b6fe3075f8f42f2270a748965bf3a1'] = 'BSD-0-Clause'
1065 crunched_md5sums['ba87a7d7c20719c8df4b8beed9b78c43'] = 'BSD-2-Clause'
1066 crunched_md5sums['7f8892c03b72de419c27be4ebfa253f8'] = 'BSD-3-Clause'
1067 crunched_md5sums['21128c0790b23a8a9f9e260d5f6b3619'] = 'BSL-1.0'
1068 crunched_md5sums['975742a59ae1b8abdea63a97121f49f4'] = 'EDL-1.0'
1069 crunched_md5sums['5322cee4433d84fb3aafc9e253116447'] = 'EPL-1.0'
1070 crunched_md5sums['6922352e87de080f42419bed93063754'] = 'EPL-2.0'
1071 crunched_md5sums['793475baa22295cae1d3d4046a3a0ceb'] = 'GPL-2.0-only'
1072 crunched_md5sums['ff9047f969b02c20f0559470df5cb433'] = 'GPL-2.0-or-later'
1073 crunched_md5sums['ea6de5453fcadf534df246e6cdafadcd'] = 'GPL-3.0-only'
1074 crunched_md5sums['b419257d4d153a6fde92ddf96acf5b67'] = 'GPL-3.0-or-later'
1075 crunched_md5sums['228737f4c49d3ee75b8fb3706b090b84'] = 'ISC'
1076 crunched_md5sums['c6a782e826ca4e85bf7f8b89435a677d'] = 'LGPL-2.0-only'
1077 crunched_md5sums['32d8f758a066752f0db09bd7624b8090'] = 'LGPL-2.0-or-later'
1078 crunched_md5sums['4820937eb198b4f84c52217ed230be33'] = 'LGPL-2.1-only'
1079 crunched_md5sums['db13fe9f3a13af7adab2dc7a76f9e44a'] = 'LGPL-2.1-or-later'
1080 crunched_md5sums['d7a0f2e4e0950e837ac3eabf5bd1d246'] = 'LGPL-3.0-only'
1081 crunched_md5sums['abbf328e2b434f9153351f06b9f79d02'] = 'LGPL-3.0-or-later'
1082 crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
1083 crunched_md5sums['b218b0e94290b9b818c4be67c8e1cc82'] = 'MIT-0'
1084 crunched_md5sums['ddc18131d6748374f0f35a621c245b49'] = 'Unlicense'
1085 crunched_md5sums['51f9570ff32571fc0a443102285c5e33'] = 'WTFPL'
1086
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001087 # The following two were gleaned from the "forever" npm package
1088 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001089 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
Andrew Geisslereff27472021-10-29 15:35:00 -05001090 crunched_md5sums['50fab24ce589d69af8964fdbfe414c60'] = 'BSD-2-Clause'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001091 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
Andrew Geisslereff27472021-10-29 15:35:00 -05001092 crunched_md5sums['88a4355858a1433fea99fae34a44da88'] = 'GPLv2'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001093 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
Andrew Geisslereff27472021-10-29 15:35:00 -05001094 crunched_md5sums['063b5c3ebb5f3aa4c85a2ed18a31fbe7'] = 'GPLv2'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001095 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
Andrew Geisslereff27472021-10-29 15:35:00 -05001096 crunched_md5sums['7f5202f4d44ed15dcd4915f5210417d8'] = 'LGPLv2.1'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001097 # unixODBC-2.3.4 COPYING
Andrew Geisslereff27472021-10-29 15:35:00 -05001098 crunched_md5sums['3debde09238a8c8e1f6a847e1ec9055b'] = 'LGPLv2.1'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001099 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
Andrew Geisslereff27472021-10-29 15:35:00 -05001100 crunched_md5sums['f90c613c51aa35da4d79dd55fc724ceb'] = 'LGPLv3'
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001101 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1102 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
Andrew Geisslereff27472021-10-29 15:35:00 -05001103
1104 # https://raw.githubusercontent.com/jquery/esprima/3.1.3/LICENSE.BSD
1105 crunched_md5sums['80fa7b56a28e8c902e6af194003220a5'] = 'BSD-2-Clause'
1106 # https://raw.githubusercontent.com/npm/npm-install-checks/master/LICENSE
1107 crunched_md5sums['e659f77bfd9002659e112d0d3d59b2c1'] = 'BSD-2-Clause'
1108 # https://raw.githubusercontent.com/silverwind/default-gateway/4.2.0/LICENSE
1109 crunched_md5sums['4c641f2d995c47f5cb08bdb4b5b6ea05'] = 'BSD-2-Clause'
1110 # https://raw.githubusercontent.com/tad-lispy/node-damerau-levenshtein/v1.0.5/LICENSE
1111 crunched_md5sums['2b8c039b2b9a25f0feb4410c4542d346'] = 'BSD-2-Clause'
1112 # https://raw.githubusercontent.com/terser/terser/v3.17.0/LICENSE
1113 crunched_md5sums['8bd23871802951c9ad63855151204c2c'] = 'BSD-2-Clause'
1114 # https://raw.githubusercontent.com/alexei/sprintf.js/1.0.3/LICENSE
1115 crunched_md5sums['008c22318c8ea65928bf730ddd0273e3'] = 'BSD-3-Clause'
1116 # https://raw.githubusercontent.com/Caligatio/jsSHA/v3.2.0/LICENSE
1117 crunched_md5sums['0e46634a01bfef056892949acaea85b1'] = 'BSD-3-Clause'
1118 # https://raw.githubusercontent.com/d3/d3-path/v1.0.9/LICENSE
1119 crunched_md5sums['b5f72aef53d3b2b432702c30b0215666'] = 'BSD-3-Clause'
1120 # https://raw.githubusercontent.com/feross/ieee754/v1.1.13/LICENSE
1121 crunched_md5sums['a39327c997c20da0937955192d86232d'] = 'BSD-3-Clause'
1122 # https://raw.githubusercontent.com/joyent/node-extsprintf/v1.3.0/LICENSE
1123 crunched_md5sums['721f23a96ff4161ca3a5f071bbe18108'] = 'MIT'
1124 # https://raw.githubusercontent.com/pvorb/clone/v0.2.0/LICENSE
1125 crunched_md5sums['b376d29a53c9573006b9970709231431'] = 'MIT'
1126 # https://raw.githubusercontent.com/andris9/encoding/v0.1.12/LICENSE
1127 crunched_md5sums['85d8a977ee9d7c5ab4ac03c9b95431c4'] = 'MIT-0'
1128 # https://raw.githubusercontent.com/faye/websocket-driver-node/0.7.3/LICENSE.md
1129 crunched_md5sums['b66384e7137e41a9b1904ef4d39703b6'] = 'Apache-2.0'
1130 # https://raw.githubusercontent.com/less/less.js/v4.1.1/LICENSE
1131 crunched_md5sums['b27575459e02221ccef97ec0bfd457ae'] = 'Apache-2.0'
1132 # https://raw.githubusercontent.com/microsoft/TypeScript/v3.5.3/LICENSE.txt
1133 crunched_md5sums['a54a1a6a39e7f9dbb4a23a42f5c7fd1c'] = 'Apache-2.0'
1134 # https://raw.githubusercontent.com/request/request/v2.87.0/LICENSE
1135 crunched_md5sums['1034431802e57486b393d00c5d262b8a'] = 'Apache-2.0'
1136 # https://raw.githubusercontent.com/dchest/tweetnacl-js/v0.14.5/LICENSE
1137 crunched_md5sums['75605e6bdd564791ab698fca65c94a4f'] = 'Unlicense'
1138 # https://raw.githubusercontent.com/stackgl/gl-mat3/v2.0.0/LICENSE.md
1139 crunched_md5sums['75512892d6f59dddb6d1c7e191957e9c'] = 'Zlib'
1140
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001141 lictext = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001142 with open(licfile, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001143 for line in f:
1144 # Drop opening statements
1145 if copyright_re.match(line):
1146 continue
Andrew Geisslereff27472021-10-29 15:35:00 -05001147 elif disclaimer_re.match(line):
1148 continue
1149 elif email_re.match(line):
1150 continue
1151 elif header_re.match(line):
1152 continue
1153 elif tag_re.match(line):
1154 continue
1155 elif url_re.match(line):
1156 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001157 elif license_title_re.match(line):
1158 continue
1159 elif license_statement_re.match(line):
1160 continue
Andrew Geisslereff27472021-10-29 15:35:00 -05001161 # Strip comment symbols
1162 line = line.replace('*', '') \
1163 .replace('#', '')
1164 # Unify spelling
1165 line = line.replace('sub-license', 'sublicense')
1166 # Squash spaces
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001167 line = oe.utils.squashspaces(line.strip())
Andrew Geisslereff27472021-10-29 15:35:00 -05001168 # Replace smart quotes, double quotes and backticks with single quotes
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001169 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
Andrew Geisslereff27472021-10-29 15:35:00 -05001170 # Unify brackets
1171 line = line.replace("{", "[").replace("}", "]")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001172 if line:
1173 lictext.append(line)
1174
1175 m = hashlib.md5()
1176 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001177 m.update(' '.join(lictext).encode('utf-8'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001178 md5val = m.hexdigest()
1179 except UnicodeEncodeError:
1180 md5val = None
1181 lictext = ''
1182 license = crunched_md5sums.get(md5val, None)
1183 return license, md5val, lictext
1184
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001185def guess_license(srctree, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001186 import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001187 md5sums = get_license_md5sums(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001188
1189 licenses = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001190 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
Andrew Geisslereff27472021-10-29 15:35:00 -05001191 skip_extensions = (".html", ".js", ".json", ".svg", ".ts")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001192 licfiles = []
1193 for root, dirs, files in os.walk(srctree):
1194 for fn in files:
Andrew Geisslereff27472021-10-29 15:35:00 -05001195 if fn.endswith(skip_extensions):
1196 continue
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001197 for spec in licspecs:
1198 if fnmatch.fnmatch(fn, spec):
1199 fullpath = os.path.join(root, fn)
1200 if not fullpath in licfiles:
1201 licfiles.append(fullpath)
1202 for licfile in licfiles:
1203 md5value = bb.utils.md5_file(licfile)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001204 license = md5sums.get(md5value, None)
1205 if not license:
1206 license, crunched_md5, lictext = crunch_license(licfile)
Andrew Geisslereff27472021-10-29 15:35:00 -05001207 if lictext and not license:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001208 license = 'Unknown'
Andrew Geisslereff27472021-10-29 15:35:00 -05001209 logger.info("Please add the following line for '%s' to a 'lib/recipetool/licenses.csv' " \
1210 "and replace `Unknown` with the license:\n" \
1211 "%s,Unknown" % (os.path.relpath(licfile, srctree), md5value))
1212 if license:
1213 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001214
1215 # FIXME should we grab at least one source file with a license header and add that too?
1216
1217 return licenses
1218
Andrew Geisslereff27472021-10-29 15:35:00 -05001219def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=[], pn='${PN}'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001220 """
1221 Given a list of (license, path, md5sum) as returned by guess_license(),
1222 a dict of package name to path mappings, write out a set of
1223 package-specific LICENSE values.
1224 """
1225 pkglicenses = {pn: []}
1226 for license, licpath, _ in licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001227 for pkgname, pkgpath in packages.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001228 if licpath.startswith(pkgpath + '/'):
1229 if pkgname in pkglicenses:
1230 pkglicenses[pkgname].append(license)
1231 else:
1232 pkglicenses[pkgname] = [license]
1233 break
1234 else:
1235 # Accumulate on the main package
1236 pkglicenses[pn].append(license)
1237 outlicenses = {}
1238 for pkgname in packages:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001239 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001240 if license == 'Unknown' and pkgname in fallback_licenses:
1241 license = fallback_licenses[pkgname]
Patrick Williams213cb262021-08-07 19:21:33 -05001242 outlines.append('LICENSE:%s = "%s"' % (pkgname, license))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001243 outlicenses[pkgname] = license.split()
1244 return outlicenses
1245
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001246def read_pkgconfig_provides(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001247 pkgdatadir = d.getVar('PKGDATA_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001248 pkgmap = {}
1249 for fn in glob.glob(os.path.join(pkgdatadir, 'shlibs2', '*.pclist')):
1250 with open(fn, 'r') as f:
1251 for line in f:
1252 pkgmap[os.path.basename(line.rstrip())] = os.path.splitext(os.path.basename(fn))[0]
1253 recipemap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001254 for pc, pkg in pkgmap.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001255 pkgdatafile = os.path.join(pkgdatadir, 'runtime', pkg)
1256 if os.path.exists(pkgdatafile):
1257 with open(pkgdatafile, 'r') as f:
1258 for line in f:
1259 if line.startswith('PN: '):
1260 recipemap[pc] = line.split(':', 1)[1].strip()
1261 return recipemap
1262
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001263def convert_debian(debpath):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001264 value_map = {'Package': 'PN',
1265 'Version': 'PV',
1266 'Section': 'SECTION',
1267 'License': 'LICENSE',
1268 'Homepage': 'HOMEPAGE'}
1269
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001270 # FIXME extend this mapping - perhaps use distro_alias.inc?
1271 depmap = {'libz-dev': 'zlib'}
1272
1273 values = {}
1274 depends = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001275 with open(os.path.join(debpath, 'control'), 'r', errors='surrogateescape') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001276 indesc = False
1277 for line in f:
1278 if indesc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001279 if line.startswith(' '):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001280 if line.startswith(' This package contains'):
1281 indesc = False
1282 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001283 if 'DESCRIPTION' in values:
1284 values['DESCRIPTION'] += ' ' + line.strip()
1285 else:
1286 values['DESCRIPTION'] = line.strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001287 else:
1288 indesc = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001289 if not indesc:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001290 splitline = line.split(':', 1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001291 if len(splitline) < 2:
1292 continue
1293 key = splitline[0]
1294 value = splitline[1].strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001295 if key == 'Build-Depends':
1296 for dep in value.split(','):
1297 dep = dep.split()[0]
1298 mapped = depmap.get(dep, '')
1299 if mapped:
1300 depends.append(mapped)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001301 elif key == 'Description':
1302 values['SUMMARY'] = value
1303 indesc = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001304 else:
1305 varname = value_map.get(key, None)
1306 if varname:
1307 values[varname] = value
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001308 postinst = os.path.join(debpath, 'postinst')
1309 postrm = os.path.join(debpath, 'postrm')
1310 preinst = os.path.join(debpath, 'preinst')
1311 prerm = os.path.join(debpath, 'prerm')
1312 sfiles = [postinst, postrm, preinst, prerm]
1313 for sfile in sfiles:
1314 if os.path.isfile(sfile):
1315 logger.info("Converting %s file to recipe function..." %
1316 os.path.basename(sfile).upper())
1317 content = []
1318 with open(sfile) as f:
1319 for line in f:
1320 if "#!/" in line:
1321 continue
1322 line = line.rstrip("\n")
1323 if line.strip():
1324 content.append(line)
1325 if content:
1326 values[os.path.basename(f.name)] = content
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001327
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001328 #if depends:
1329 # values['DEPENDS'] = ' '.join(depends)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001330
1331 return values
1332
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001333def convert_rpm_xml(xmlfile):
1334 '''Converts the output from rpm -qp --xml to a set of variable values'''
1335 import xml.etree.ElementTree as ElementTree
1336 rpmtag_map = {'Name': 'PN',
1337 'Version': 'PV',
1338 'Summary': 'SUMMARY',
1339 'Description': 'DESCRIPTION',
1340 'License': 'LICENSE',
1341 'Url': 'HOMEPAGE'}
1342
1343 values = {}
1344 tree = ElementTree.parse(xmlfile)
1345 root = tree.getroot()
1346 for child in root:
1347 if child.tag == 'rpmTag':
1348 name = child.attrib.get('name', None)
1349 if name:
1350 varname = rpmtag_map.get(name, None)
1351 if varname:
1352 values[varname] = child[0].text
1353 return values
1354
1355
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001356def register_commands(subparsers):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001357 parser_create = subparsers.add_parser('create',
1358 help='Create a new recipe',
1359 description='Creates a new recipe from a source tree')
1360 parser_create.add_argument('source', help='Path or URL to source')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001361 parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001362 parser_create.add_argument('-p', '--provides', help='Specify an alias for the item provided by the recipe')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001363 parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
1364 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 -05001365 parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001366 parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001367 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 -05001368 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')
1369 parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001370 group = parser_create.add_mutually_exclusive_group()
1371 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")
1372 group.add_argument('-S', '--srcrev', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
1373 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 -06001374 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Andrew Geissler82c905d2020-04-13 13:39:40 -05001375 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies')
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001376 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001377 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1378 parser_create.set_defaults(func=create_recipe)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001379