blob: d427d32062c979e4f41ef3707eb2877bc72f62c5 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Recipe creation tool - create command plugin
2#
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05003# Copyright (C) 2014-2016 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18import sys
19import os
20import argparse
21import glob
22import fnmatch
23import re
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050024import json
Patrick Williamsc124f4f2015-09-15 14:41:29 -050025import logging
26import scriptutils
Patrick Williamsc0f7c042017-02-23 20:41:17 -060027from urllib.parse import urlparse, urldefrag, urlsplit
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050028import hashlib
Patrick Williamsc124f4f2015-09-15 14:41:29 -050029
30logger = logging.getLogger('recipetool')
31
32tinfoil = None
33plugins = None
34
35def plugin_init(pluginlist):
36 # Take a reference to the list so we can use it later
37 global plugins
38 plugins = pluginlist
39
40def tinfoil_init(instance):
41 global tinfoil
42 tinfoil = instance
43
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050044class RecipeHandler(object):
45 recipelibmap = {}
46 recipeheadermap = {}
47 recipecmakefilemap = {}
48 recipebinmap = {}
49
Patrick Williamsc124f4f2015-09-15 14:41:29 -050050 @staticmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050051 def load_libmap(d):
52 '''Load library->recipe mapping'''
53 import oe.package
54
55 if RecipeHandler.recipelibmap:
56 return
57 # First build up library->package mapping
58 shlib_providers = oe.package.read_shlib_providers(d)
59 libdir = d.getVar('libdir', True)
60 base_libdir = d.getVar('base_libdir', True)
61 libpaths = list(set([base_libdir, libdir]))
62 libname_re = re.compile('^lib(.+)\.so.*$')
63 pkglibmap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -060064 for lib, item in shlib_providers.items():
65 for path, pkg in item.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050066 if path in libpaths:
67 res = libname_re.match(lib)
68 if res:
69 libname = res.group(1)
70 if not libname in pkglibmap:
71 pkglibmap[libname] = pkg[0]
72 else:
73 logger.debug('unable to extract library name from %s' % lib)
74
75 # Now turn it into a library->recipe mapping
76 pkgdata_dir = d.getVar('PKGDATA_DIR', True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060077 for libname, pkg in pkglibmap.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050078 try:
79 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
80 for line in f:
81 if line.startswith('PN:'):
82 RecipeHandler.recipelibmap[libname] = line.split(':', 1)[-1].strip()
83 break
84 except IOError as ioe:
85 if ioe.errno == 2:
86 logger.warn('unable to find a pkgdata file for package %s' % pkg)
87 else:
88 raise
89
90 # Some overrides - these should be mapped to the virtual
91 RecipeHandler.recipelibmap['GL'] = 'virtual/libgl'
92 RecipeHandler.recipelibmap['EGL'] = 'virtual/egl'
93 RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2'
94
95 @staticmethod
96 def load_devel_filemap(d):
97 '''Build up development file->recipe mapping'''
98 if RecipeHandler.recipeheadermap:
99 return
100 pkgdata_dir = d.getVar('PKGDATA_DIR', True)
101 includedir = d.getVar('includedir', True)
102 cmakedir = os.path.join(d.getVar('libdir', True), 'cmake')
103 for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')):
104 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
105 pn = None
106 headers = []
107 cmakefiles = []
108 for line in f:
109 if line.startswith('PN:'):
110 pn = line.split(':', 1)[-1].strip()
111 elif line.startswith('FILES_INFO:'):
112 val = line.split(':', 1)[1].strip()
113 dictval = json.loads(val)
114 for fullpth in sorted(dictval):
115 if fullpth.startswith(includedir) and fullpth.endswith('.h'):
116 headers.append(os.path.relpath(fullpth, includedir))
117 elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'):
118 cmakefiles.append(os.path.relpath(fullpth, cmakedir))
119 if pn and headers:
120 for header in headers:
121 RecipeHandler.recipeheadermap[header] = pn
122 if pn and cmakefiles:
123 for fn in cmakefiles:
124 RecipeHandler.recipecmakefilemap[fn] = pn
125
126 @staticmethod
127 def load_binmap(d):
128 '''Build up native binary->recipe mapping'''
129 if RecipeHandler.recipebinmap:
130 return
131 sstate_manifests = d.getVar('SSTATE_MANIFESTS', True)
132 staging_bindir_native = d.getVar('STAGING_BINDIR_NATIVE', True)
133 build_arch = d.getVar('BUILD_ARCH', True)
134 fileprefix = 'manifest-%s-' % build_arch
135 for fn in glob.glob(os.path.join(sstate_manifests, '%s*-native.populate_sysroot' % fileprefix)):
136 with open(fn, 'r') as f:
137 pn = os.path.basename(fn).rsplit('.', 1)[0][len(fileprefix):]
138 for line in f:
139 if line.startswith(staging_bindir_native):
140 prog = os.path.basename(line.rstrip())
141 RecipeHandler.recipebinmap[prog] = pn
142
143 @staticmethod
144 def checkfiles(path, speclist, recursive=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500145 results = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500146 if recursive:
147 for root, _, files in os.walk(path):
148 for fn in files:
149 for spec in speclist:
150 if fnmatch.fnmatch(fn, spec):
151 results.append(os.path.join(root, fn))
152 else:
153 for spec in speclist:
154 results.extend(glob.glob(os.path.join(path, spec)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155 return results
156
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500157 @staticmethod
158 def handle_depends(libdeps, pcdeps, deps, outlines, values, d):
159 if pcdeps:
160 recipemap = read_pkgconfig_provides(d)
161 if libdeps:
162 RecipeHandler.load_libmap(d)
163
164 ignorelibs = ['socket']
165 ignoredeps = ['gcc-runtime', 'glibc', 'uclibc', 'musl', 'tar-native', 'binutils-native', 'coreutils-native']
166
167 unmappedpc = []
168 pcdeps = list(set(pcdeps))
169 for pcdep in pcdeps:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600170 if isinstance(pcdep, str):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500171 recipe = recipemap.get(pcdep, None)
172 if recipe:
173 deps.append(recipe)
174 else:
175 if not pcdep.startswith('$'):
176 unmappedpc.append(pcdep)
177 else:
178 for item in pcdep:
179 recipe = recipemap.get(pcdep, None)
180 if recipe:
181 deps.append(recipe)
182 break
183 else:
184 unmappedpc.append('(%s)' % ' or '.join(pcdep))
185
186 unmappedlibs = []
187 for libdep in libdeps:
188 if isinstance(libdep, tuple):
189 lib, header = libdep
190 else:
191 lib = libdep
192 header = None
193
194 if lib in ignorelibs:
195 logger.debug('Ignoring library dependency %s' % lib)
196 continue
197
198 recipe = RecipeHandler.recipelibmap.get(lib, None)
199 if recipe:
200 deps.append(recipe)
201 elif recipe is None:
202 if header:
203 RecipeHandler.load_devel_filemap(d)
204 recipe = RecipeHandler.recipeheadermap.get(header, None)
205 if recipe:
206 deps.append(recipe)
207 elif recipe is None:
208 unmappedlibs.append(lib)
209 else:
210 unmappedlibs.append(lib)
211
212 deps = set(deps).difference(set(ignoredeps))
213
214 if unmappedpc:
215 outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmappedpc))
216 outlines.append('# (this is based on recipes that have previously been built and packaged)')
217
218 if unmappedlibs:
219 outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmappedlibs))))
220 outlines.append('# (this is based on recipes that have previously been built and packaged)')
221
222 if deps:
223 values['DEPENDS'] = ' '.join(deps)
224
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500225 def genfunction(self, outlines, funcname, content, python=False, forcespace=False):
226 if python:
227 prefix = 'python '
228 else:
229 prefix = ''
230 outlines.append('%s%s () {' % (prefix, funcname))
231 if python or forcespace:
232 indent = ' '
233 else:
234 indent = '\t'
235 addnoop = not python
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500236 for line in content:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500237 outlines.append('%s%s' % (indent, line))
238 if addnoop:
239 strippedline = line.lstrip()
240 if strippedline and not strippedline.startswith('#'):
241 addnoop = False
242 if addnoop:
243 # Without this there'll be a syntax error
244 outlines.append('%s:' % indent)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500245 outlines.append('}')
246 outlines.append('')
247
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500248 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500249 return False
250
251
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500252def validate_pv(pv):
253 if not pv or '_version' in pv.lower() or pv[0] not in '0123456789':
254 return False
255 return True
256
257def determine_from_filename(srcfile):
258 """Determine name and version from a filename"""
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600259 if is_package(srcfile):
260 # Force getting the value from the package metadata
261 return None, None
262
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500263 if '.tar.' in srcfile:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600264 namepart = srcfile.split('.tar.')[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500265 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600266 namepart = os.path.splitext(srcfile)[0]
267 namepart = namepart.lower().replace('_', '-')
268 if namepart.endswith('.src'):
269 namepart = namepart[:-4]
270 if namepart.endswith('.orig'):
271 namepart = namepart[:-5]
272 splitval = namepart.split('-')
273 logger.debug('determine_from_filename: split name %s into: %s' % (srcfile, splitval))
274
275 ver_re = re.compile('^v?[0-9]')
276
277 pv = None
278 pn = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500279 if len(splitval) == 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600280 # Try to split the version out if there is no separator (or a .)
281 res = re.match('^([^0-9]+)([0-9.]+.*)$', namepart)
282 if res:
283 if len(res.group(1)) > 1 and len(res.group(2)) > 1:
284 pn = res.group(1).rstrip('.')
285 pv = res.group(2)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500286 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600287 pn = namepart
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500288 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600289 if splitval[-1] in ['source', 'src']:
290 splitval.pop()
291 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]):
292 pv = '-'.join(splitval[-2:])
293 if pv.endswith('-release'):
294 pv = pv[:-8]
295 splitval = splitval[:-2]
296 elif ver_re.match(splitval[-1]):
297 pv = splitval.pop()
298 pn = '-'.join(splitval)
299 if pv and pv.startswith('v'):
300 pv = pv[1:]
301 logger.debug('determine_from_filename: name = "%s" version = "%s"' % (pn, pv))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500302 return (pn, pv)
303
304def determine_from_url(srcuri):
305 """Determine name and version from a URL"""
306 pn = None
307 pv = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600308 parseres = urlparse(srcuri.lower().split(';', 1)[0])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500309 if parseres.path:
310 if 'github.com' in parseres.netloc:
311 res = re.search(r'.*/(.*?)/archive/(.*)-final\.(tar|zip)', parseres.path)
312 if res:
313 pn = res.group(1).strip().replace('_', '-')
314 pv = res.group(2).strip().replace('_', '.')
315 else:
316 res = re.search(r'.*/(.*?)/archive/v?(.*)\.(tar|zip)', parseres.path)
317 if res:
318 pn = res.group(1).strip().replace('_', '-')
319 pv = res.group(2).strip().replace('_', '.')
320 elif 'bitbucket.org' in parseres.netloc:
321 res = re.search(r'.*/(.*?)/get/[a-zA-Z_-]*([0-9][0-9a-zA-Z_.]*)\.(tar|zip)', parseres.path)
322 if res:
323 pn = res.group(1).strip().replace('_', '-')
324 pv = res.group(2).strip().replace('_', '.')
325
326 if not pn and not pv:
327 srcfile = os.path.basename(parseres.path.rstrip('/'))
328 pn, pv = determine_from_filename(srcfile)
329
330 logger.debug('Determined from source URL: name = "%s", version = "%s"' % (pn, pv))
331 return (pn, pv)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500332
333def supports_srcrev(uri):
334 localdata = bb.data.createCopy(tinfoil.config_data)
335 # This is a bit sad, but if you don't have this set there can be some
336 # odd interactions with the urldata cache which lead to errors
337 localdata.setVar('SRCREV', '${AUTOREV}')
338 bb.data.update_data(localdata)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600339 try:
340 fetcher = bb.fetch2.Fetch([uri], localdata)
341 urldata = fetcher.ud
342 for u in urldata:
343 if urldata[u].method.supports_srcrev():
344 return True
345 except bb.fetch2.FetchError as e:
346 logger.debug('FetchError in supports_srcrev: %s' % str(e))
347 # Fall back to basic check
348 if uri.startswith(('git://', 'gitsm://')):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349 return True
350 return False
351
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500352def reformat_git_uri(uri):
353 '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
354 checkuri = uri.split(';', 1)[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600355 if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://github.com/[^/]+/[^/]+/?$', checkuri):
356 res = re.match('(http|https|ssh)://([^;]+(\.git)?)(;.*)?$', uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500357 if res:
358 # Need to switch the URI around so that the git fetcher is used
359 return 'git://%s;protocol=%s%s' % (res.group(2), res.group(1), res.group(4) or '')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600360 elif '@' in checkuri:
361 # Catch e.g. git@git.example.com:repo.git
362 return 'git://%s;protocol=ssh' % checkuri.replace(':', '/', 1)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500363 return uri
364
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600365def is_package(url):
366 '''Check if a URL points to a package'''
367 checkurl = url.split(';', 1)[0]
368 if checkurl.endswith(('.deb', '.ipk', '.rpm', '.srpm')):
369 return True
370 return False
371
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500372def create_recipe(args):
373 import bb.process
374 import tempfile
375 import shutil
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600376 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500377
378 pkgarch = ""
379 if args.machine:
380 pkgarch = "${MACHINE_ARCH}"
381
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600382 extravalues = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500383 checksums = (None, None)
384 tempsrc = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600385 source = args.source
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500386 srcsubdir = ''
387 srcrev = '${AUTOREV}'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600388
389 if os.path.isfile(source):
390 source = 'file://%s' % os.path.abspath(source)
391
392 if scriptutils.is_src_url(source):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500393 # Fetch a URL
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600394 fetchuri = reformat_git_uri(urldefrag(source)[0])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500395 if args.binary:
396 # Assume the archive contains the directory structure verbatim
397 # so we need to extract to a subdirectory
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600398 fetchuri += ';subdir=${BP}'
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500399 srcuri = fetchuri
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500400 rev_re = re.compile(';rev=([^;]+)')
401 res = rev_re.search(srcuri)
402 if res:
403 srcrev = res.group(1)
404 srcuri = rev_re.sub('', srcuri)
405 tempsrc = tempfile.mkdtemp(prefix='recipetool-')
406 srctree = tempsrc
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500407 if fetchuri.startswith('npm://'):
408 # Check if npm is available
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600409 check_npm(tinfoil.config_data)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500410 logger.info('Fetching %s...' % srcuri)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500411 try:
412 checksums = scriptutils.fetch_uri(tinfoil.config_data, fetchuri, srctree, srcrev)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500413 except bb.fetch2.BBFetchException as e:
414 logger.error(str(e).rstrip())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500415 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416 dirlist = os.listdir(srctree)
417 if 'git.indirectionsymlink' in dirlist:
418 dirlist.remove('git.indirectionsymlink')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500419 if len(dirlist) == 1:
420 singleitem = os.path.join(srctree, dirlist[0])
421 if os.path.isdir(singleitem):
422 # We unpacked a single directory, so we should use that
423 srcsubdir = dirlist[0]
424 srctree = os.path.join(srctree, srcsubdir)
425 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600426 with open(singleitem, 'r', errors='surrogateescape') as f:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500427 if '<html' in f.read(100).lower():
428 logger.error('Fetching "%s" returned a single HTML page - check the URL is correct and functional' % fetchuri)
429 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600430 if os.path.exists(os.path.join(srctree, '.gitmodules')) and srcuri.startswith('git://'):
431 srcuri = 'gitsm://' + srcuri[6:]
432 logger.info('Fetching submodules...')
433 bb.process.run('git submodule update --init --recursive', cwd=srctree)
434
435 if is_package(fetchuri):
436 tmpfdir = tempfile.mkdtemp(prefix='recipetool-')
437 try:
438 pkgfile = None
439 try:
440 fileuri = fetchuri + ';unpack=0'
441 scriptutils.fetch_uri(tinfoil.config_data, fileuri, tmpfdir, srcrev)
442 for root, _, files in os.walk(tmpfdir):
443 for f in files:
444 pkgfile = os.path.join(root, f)
445 break
446 except bb.fetch2.BBFetchException as e:
447 logger.warn('Second fetch to get metadata failed: %s' % str(e).rstrip())
448
449 if pkgfile:
450 if pkgfile.endswith(('.deb', '.ipk')):
451 stdout, _ = bb.process.run('ar x %s control.tar.gz' % pkgfile, cwd=tmpfdir)
452 stdout, _ = bb.process.run('tar xf control.tar.gz ./control', cwd=tmpfdir)
453 values = convert_debian(tmpfdir)
454 extravalues.update(values)
455 elif pkgfile.endswith(('.rpm', '.srpm')):
456 stdout, _ = bb.process.run('rpm -qp --xml %s > pkginfo.xml' % pkgfile, cwd=tmpfdir)
457 values = convert_rpm_xml(os.path.join(tmpfdir, 'pkginfo.xml'))
458 extravalues.update(values)
459 finally:
460 shutil.rmtree(tmpfdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500461 else:
462 # Assume we're pointing to an existing source tree
463 if args.extract_to:
464 logger.error('--extract-to cannot be specified if source is a directory')
465 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600466 if not os.path.isdir(source):
467 logger.error('Invalid source directory %s' % source)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500468 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600469 srctree = source
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500470 srcuri = ''
471 if os.path.exists(os.path.join(srctree, '.git')):
472 # Try to get upstream repo location from origin remote
473 try:
474 stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
475 except bb.process.ExecutionError as e:
476 stdout = None
477 if stdout:
478 for line in stdout.splitlines():
479 splitline = line.split()
480 if len(splitline) > 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600481 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500482 srcuri = reformat_git_uri(splitline[1])
483 srcsubdir = 'git'
484 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500485
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500486 if args.src_subdir:
487 srcsubdir = os.path.join(srcsubdir, args.src_subdir)
488 srctree_use = os.path.join(srctree, args.src_subdir)
489 else:
490 srctree_use = srctree
491
492 if args.outfile and os.path.isdir(args.outfile):
493 outfile = None
494 outdir = args.outfile
495 else:
496 outfile = args.outfile
497 outdir = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500498 if outfile and outfile != '-':
499 if os.path.exists(outfile):
500 logger.error('Output file %s already exists' % outfile)
501 sys.exit(1)
502
503 lines_before = []
504 lines_after = []
505
506 lines_before.append('# Recipe created by %s' % os.path.basename(sys.argv[0]))
507 lines_before.append('# This is the basis of a recipe and may need further editing in order to be fully functional.')
508 lines_before.append('# (Feel free to remove these comments when editing.)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600509 # We need a blank line here so that patch_recipe_lines can rewind before the LICENSE comments
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500510 lines_before.append('')
511
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600512 handled = []
513 licvalues = handle_license_vars(srctree_use, lines_before, handled, extravalues, tinfoil.config_data)
514
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500515 classes = []
516
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500517 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500518 pn = None
519 pv = None
520 if outfile:
521 recipefn = os.path.splitext(os.path.basename(outfile))[0]
522 fnsplit = recipefn.split('_')
523 if len(fnsplit) > 1:
524 pn = fnsplit[0]
525 pv = fnsplit[1]
526 else:
527 pn = recipefn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500528
529 if args.version:
530 pv = args.version
531
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500532 if args.name:
533 pn = args.name
534 if args.name.endswith('-native'):
535 if args.also_native:
536 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')
537 sys.exit(1)
538 classes.append('native')
539 elif args.name.startswith('nativesdk-'):
540 if args.also_native:
541 logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
542 sys.exit(1)
543 classes.append('nativesdk')
544
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500545 if pv and pv not in 'git svn hg'.split():
546 realpv = pv
547 else:
548 realpv = None
549
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500550 if srcuri and not realpv or not pn:
551 name_pn, name_pv = determine_from_url(srcuri)
552 if name_pn and not pn:
553 pn = name_pn
554 if name_pv and not realpv:
555 realpv = name_pv
556
557
558 if not srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500559 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
560 lines_before.append('SRC_URI = "%s"' % srcuri)
561 (md5value, sha256value) = checksums
562 if md5value:
563 lines_before.append('SRC_URI[md5sum] = "%s"' % md5value)
564 if sha256value:
565 lines_before.append('SRC_URI[sha256sum] = "%s"' % sha256value)
566 if srcuri and supports_srcrev(srcuri):
567 lines_before.append('')
568 lines_before.append('# Modify these as desired')
569 lines_before.append('PV = "%s+git${SRCPV}"' % (realpv or '1.0'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600570 if not args.autorev and srcrev == '${AUTOREV}':
571 if os.path.exists(os.path.join(srctree, '.git')):
572 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
573 srcrev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500574 lines_before.append('SRCREV = "%s"' % srcrev)
575 lines_before.append('')
576
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600577 if srcsubdir and not args.binary:
578 # (for binary packages we explicitly specify subdir= when fetching to
579 # match the default value of S, so we don't need to set it in that case)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500580 lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
581 lines_before.append('')
582
583 if pkgarch:
584 lines_after.append('PACKAGE_ARCH = "%s"' % pkgarch)
585 lines_after.append('')
586
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500587 if args.binary:
588 lines_after.append('INSANE_SKIP_${PN} += "already-stripped"')
589 lines_after.append('')
590
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591 # Find all plugins that want to register handlers
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500592 logger.debug('Loading recipe handlers')
593 raw_handlers = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 for plugin in plugins:
595 if hasattr(plugin, 'register_recipe_handlers'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500596 plugin.register_recipe_handlers(raw_handlers)
597 # Sort handlers by priority
598 handlers = []
599 for i, handler in enumerate(raw_handlers):
600 if isinstance(handler, tuple):
601 handlers.append((handler[0], handler[1], i))
602 else:
603 handlers.append((handler, 0, i))
604 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
605 for handler, priority, _ in handlers:
606 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
607 handlers = [item[0] for item in handlers]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500608
609 # Apply the handlers
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500610 if args.binary:
611 classes.append('bin_package')
612 handled.append('buildsystem')
613
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500614 for handler in handlers:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500615 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
616
617 extrafiles = extravalues.pop('extrafiles', {})
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600618 extra_pn = extravalues.pop('PN', None)
619 extra_pv = extravalues.pop('PV', None)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500620
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600621 if extra_pv and not realpv:
622 realpv = extra_pv
623 if not validate_pv(realpv):
624 realpv = None
625 else:
626 realpv = realpv.lower().split()[0]
627 if '_' in realpv:
628 realpv = realpv.replace('_', '-')
629 if extra_pn and not pn:
630 pn = extra_pn
631 if pn.startswith('GNU '):
632 pn = pn[4:]
633 if ' ' in pn:
634 # Probably a descriptive identifier rather than a proper name
635 pn = None
636 else:
637 pn = pn.lower()
638 if '_' in pn:
639 pn = pn.replace('_', '-')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500640
641 if not outfile:
642 if not pn:
643 logger.error('Unable to determine short program name from source tree - please specify name with -N/--name or output file name with -o/--outfile')
644 # devtool looks for this specific exit code, so don't change it
645 sys.exit(15)
646 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600647 if srcuri and srcuri.startswith(('gitsm://', 'git://', 'hg://', 'svn://')):
648 suffix = srcuri.split(':', 1)[0]
649 if suffix == 'gitsm':
650 suffix = 'git'
651 outfile = '%s_%s.bb' % (pn, suffix)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500652 elif realpv:
653 outfile = '%s_%s.bb' % (pn, realpv)
654 else:
655 outfile = '%s.bb' % pn
656 if outdir:
657 outfile = os.path.join(outdir, outfile)
658 # We need to check this again
659 if os.path.exists(outfile):
660 logger.error('Output file %s already exists' % outfile)
661 sys.exit(1)
662
663 # Move any extra files the plugins created to a directory next to the recipe
664 if extrafiles:
665 if outfile == '-':
666 extraoutdir = pn
667 else:
668 extraoutdir = os.path.join(os.path.dirname(outfile), pn)
669 bb.utils.mkdirhier(extraoutdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600670 for destfn, extrafile in extrafiles.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500671 shutil.move(extrafile, os.path.join(extraoutdir, destfn))
672
673 lines = lines_before
674 lines_before = []
675 skipblank = True
676 for line in lines:
677 if skipblank:
678 skipblank = False
679 if not line:
680 continue
681 if line.startswith('S = '):
682 if realpv and pv not in 'git svn hg'.split():
683 line = line.replace(realpv, '${PV}')
684 if pn:
685 line = line.replace(pn, '${BPN}')
686 if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
687 skipblank = True
688 continue
689 elif line.startswith('SRC_URI = '):
690 if realpv:
691 line = line.replace(realpv, '${PV}')
692 elif line.startswith('PV = '):
693 if realpv:
694 line = re.sub('"[^+]*\+', '"%s+' % realpv, line)
695 lines_before.append(line)
696
697 if args.also_native:
698 lines = lines_after
699 lines_after = []
700 bbclassextend = None
701 for line in lines:
702 if line.startswith('BBCLASSEXTEND ='):
703 splitval = line.split('"')
704 if len(splitval) > 1:
705 bbclassextend = splitval[1].split()
706 if not 'native' in bbclassextend:
707 bbclassextend.insert(0, 'native')
708 line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
709 lines_after.append(line)
710 if not bbclassextend:
711 lines_after.append('BBCLASSEXTEND = "native"')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500712
713 outlines = []
714 outlines.extend(lines_before)
715 if classes:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500716 if outlines[-1] and not outlines[-1].startswith('#'):
717 outlines.append('')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500718 outlines.append('inherit %s' % ' '.join(classes))
719 outlines.append('')
720 outlines.extend(lines_after)
721
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600722 if extravalues:
723 if 'LICENSE' in extravalues and not licvalues:
724 # Don't blow away 'CLOSED' value that comments say we set
725 del extravalues['LICENSE']
726 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False)
727
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500728 if args.extract_to:
729 scriptutils.git_convert_standalone_clone(srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500730 if os.path.isdir(args.extract_to):
731 # If the directory exists we'll move the temp dir into it instead of
732 # its contents - of course, we could try to always move its contents
733 # but that is a pain if there are symlinks; the simplest solution is
734 # to just remove it first
735 os.rmdir(args.extract_to)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500736 shutil.move(srctree, args.extract_to)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500737 if tempsrc == srctree:
738 tempsrc = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500739 logger.info('Source extracted to %s' % args.extract_to)
740
741 if outfile == '-':
742 sys.stdout.write('\n'.join(outlines) + '\n')
743 else:
744 with open(outfile, 'w') as f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600745 lastline = None
746 for line in outlines:
747 if not lastline and not line:
748 # Skip extra blank lines
749 continue
750 f.write('%s\n' % line)
751 lastline = line
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500752 logger.info('Recipe %s has been created; further editing may be required to make it fully functional' % outfile)
753
754 if tempsrc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600755 if args.keep_temp:
756 logger.info('Preserving temporary directory %s' % tempsrc)
757 else:
758 shutil.rmtree(tempsrc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500759
760 return 0
761
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600762def handle_license_vars(srctree, lines_before, handled, extravalues, d):
763 licvalues = guess_license(srctree, d)
764 lic_files_chksum = []
765 lic_unknown = []
766 if licvalues:
767 licenses = []
768 for licvalue in licvalues:
769 if not licvalue[0] in licenses:
770 licenses.append(licvalue[0])
771 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
772 if licvalue[0] == 'Unknown':
773 lic_unknown.append(licvalue[1])
774 lines_before.append('# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is')
775 lines_before.append('# your responsibility to verify that the values are complete and correct.')
776 if len(licvalues) > 1:
777 lines_before.append('#')
778 lines_before.append('# NOTE: multiple licenses have been detected; if that is correct you should separate')
779 lines_before.append('# these in the LICENSE value using & if the multiple licenses all apply, or | if there')
780 lines_before.append('# is a choice between the multiple licenses. If in doubt, check the accompanying')
781 lines_before.append('# documentation to determine which situation is applicable.')
782 if lic_unknown:
783 lines_before.append('#')
784 lines_before.append('# The following license files were not able to be identified and are')
785 lines_before.append('# represented as "Unknown" below, you will need to check them yourself:')
786 for licfile in lic_unknown:
787 lines_before.append('# %s' % licfile)
788 lines_before.append('#')
789 else:
790 lines_before.append('# Unable to find any files that looked like license statements. Check the accompanying')
791 lines_before.append('# documentation and source headers and set LICENSE and LIC_FILES_CHKSUM accordingly.')
792 lines_before.append('#')
793 lines_before.append('# NOTE: LICENSE is being set to "CLOSED" to allow you to at least start building - if')
794 lines_before.append('# this is not accurate with respect to the licensing of the software being built (it')
795 lines_before.append('# will not be in most cases) you must specify the correct value before using this')
796 lines_before.append('# recipe for anything other than initial testing/development!')
797 licenses = ['CLOSED']
798 pkg_license = extravalues.pop('LICENSE', None)
799 if pkg_license:
800 if licenses == ['Unknown']:
801 lines_before.append('# NOTE: The following LICENSE value was determined from the original package metadata')
802 licenses = [pkg_license]
803 else:
804 lines_before.append('# NOTE: Original package metadata indicates license is: %s' % pkg_license)
805 lines_before.append('LICENSE = "%s"' % ' '.join(licenses))
806 lines_before.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
807 lines_before.append('')
808 handled.append(('license', licvalues))
809 return licvalues
810
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500811def get_license_md5sums(d, static_only=False):
812 import bb.utils
813 md5sums = {}
814 if not static_only:
815 # Gather md5sums of license files in common license dir
816 commonlicdir = d.getVar('COMMON_LICENSE_DIR', True)
817 for fn in os.listdir(commonlicdir):
818 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
819 md5sums[md5value] = fn
820 # The following were extracted from common values in various recipes
821 # (double checking the license against the license file itself, not just
822 # the LICENSE value in the recipe)
823 md5sums['94d55d512a9ba36caa9b7df079bae19f'] = 'GPLv2'
824 md5sums['b234ee4d69f5fce4486a80fdaf4a4263'] = 'GPLv2'
825 md5sums['59530bdf33659b29e73d4adb9f9f6552'] = 'GPLv2'
826 md5sums['0636e73ff0215e8d672dc4c32c317bb3'] = 'GPLv2'
827 md5sums['eb723b61539feef013de476e68b5c50a'] = 'GPLv2'
828 md5sums['751419260aa954499f7abaabaa882bbe'] = 'GPLv2'
829 md5sums['393a5ca445f6965873eca0259a17f833'] = 'GPLv2'
830 md5sums['12f884d2ae1ff87c09e5b7ccc2c4ca7e'] = 'GPLv2'
831 md5sums['8ca43cbc842c2336e835926c2166c28b'] = 'GPLv2'
832 md5sums['ebb5c50ab7cab4baeffba14977030c07'] = 'GPLv2'
833 md5sums['c93c0550bd3173f4504b2cbd8991e50b'] = 'GPLv2'
834 md5sums['9ac2e7cff1ddaf48b6eab6028f23ef88'] = 'GPLv2'
835 md5sums['4325afd396febcb659c36b49533135d4'] = 'GPLv2'
836 md5sums['18810669f13b87348459e611d31ab760'] = 'GPLv2'
837 md5sums['d7810fab7487fb0aad327b76f1be7cd7'] = 'GPLv2' # the Linux kernel's COPYING file
838 md5sums['bbb461211a33b134d42ed5ee802b37ff'] = 'LGPLv2.1'
839 md5sums['7fbc338309ac38fefcd64b04bb903e34'] = 'LGPLv2.1'
840 md5sums['4fbd65380cdd255951079008b364516c'] = 'LGPLv2.1'
841 md5sums['2d5025d4aa3495befef8f17206a5b0a1'] = 'LGPLv2.1'
842 md5sums['fbc093901857fcd118f065f900982c24'] = 'LGPLv2.1'
843 md5sums['a6f89e2100d9b6cdffcea4f398e37343'] = 'LGPLv2.1'
844 md5sums['d8045f3b8f929c1cb29a1e3fd737b499'] = 'LGPLv2.1'
845 md5sums['fad9b3332be894bab9bc501572864b29'] = 'LGPLv2.1'
846 md5sums['3bf50002aefd002f49e7bb854063f7e7'] = 'LGPLv2'
847 md5sums['9f604d8a4f8e74f4f5140845a21b6674'] = 'LGPLv2'
848 md5sums['5f30f0716dfdd0d91eb439ebec522ec2'] = 'LGPLv2'
849 md5sums['55ca817ccb7d5b5b66355690e9abc605'] = 'LGPLv2'
850 md5sums['252890d9eee26aab7b432e8b8a616475'] = 'LGPLv2'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500851 md5sums['3214f080875748938ba060314b4f727d'] = 'LGPLv2'
852 md5sums['db979804f025cf55aabec7129cb671ed'] = 'LGPLv2'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500853 md5sums['d32239bcb673463ab874e80d47fae504'] = 'GPLv3'
854 md5sums['f27defe1e96c2e1ecd4e0c9be8967949'] = 'GPLv3'
855 md5sums['6a6a8e020838b23406c81b19c1d46df6'] = 'LGPLv3'
856 md5sums['3b83ef96387f14655fc854ddc3c6bd57'] = 'Apache-2.0'
857 md5sums['385c55653886acac3821999a3ccd17b3'] = 'Artistic-1.0 | GPL-2.0' # some perl modules
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500858 md5sums['54c7042be62e169199200bc6477f04d1'] = 'BSD-3-Clause'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500859 return md5sums
860
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500861def crunch_license(licfile):
862 '''
863 Remove non-material text from a license file and then check
864 its md5sum against a known list. This works well for licenses
865 which contain a copyright statement, but is also a useful way
866 to handle people's insistence upon reformatting the license text
867 slightly (with no material difference to the text of the
868 license).
869 '''
870
871 import oe.utils
872
873 # Note: these are carefully constructed!
874 license_title_re = re.compile('^\(?(#+ *)?(The )?.{1,10} [Ll]icen[sc]e( \(.{1,10}\))?\)?:?$')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600875 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 -0500876 copyright_re = re.compile('^(#+)? *Copyright .*$')
877
878 crunched_md5sums = {}
879 # The following two were gleaned from the "forever" npm package
880 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
881 crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
882 # https://github.com/vasi/pixz/blob/master/LICENSE
883 crunched_md5sums['2f03392b40bbe663597b5bd3cc5ebdb9'] = 'BSD-2-Clause'
884 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
885 crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause'
886 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
887 crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2'
888 # https://github.com/datto/dattobd/blob/master/COPYING
889 # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT
890 crunched_md5sums['1d65c5ad4bf6489f85f4812bf08ae73d'] = 'GPLv2'
891 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
892 # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD
893 crunched_md5sums['fb530f66a7a89ce920f0e912b5b66d4b'] = 'GPLv2'
894 # https://github.com/gkos/nrf24/blob/master/COPYING
895 crunched_md5sums['7b6aaa4daeafdfa6ed5443fd2684581b'] = 'GPLv2'
896 # https://github.com/josch09/resetusb/blob/master/COPYING
897 crunched_md5sums['8b8ac1d631a4d220342e83bcf1a1fbc3'] = 'GPLv3'
898 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
899 crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1'
900 # unixODBC-2.3.4 COPYING
901 crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1'
902 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
903 crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3'
904 lictext = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600905 with open(licfile, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500906 for line in f:
907 # Drop opening statements
908 if copyright_re.match(line):
909 continue
910 elif license_title_re.match(line):
911 continue
912 elif license_statement_re.match(line):
913 continue
914 # Squash spaces, and replace smart quotes, double quotes
915 # and backticks with single quotes
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600916 line = oe.utils.squashspaces(line.strip())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500917 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
918 if line:
919 lictext.append(line)
920
921 m = hashlib.md5()
922 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600923 m.update(' '.join(lictext).encode('utf-8'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500924 md5val = m.hexdigest()
925 except UnicodeEncodeError:
926 md5val = None
927 lictext = ''
928 license = crunched_md5sums.get(md5val, None)
929 return license, md5val, lictext
930
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600931def guess_license(srctree, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500932 import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600933 md5sums = get_license_md5sums(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500934
935 licenses = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500936 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500937 licfiles = []
938 for root, dirs, files in os.walk(srctree):
939 for fn in files:
940 for spec in licspecs:
941 if fnmatch.fnmatch(fn, spec):
942 fullpath = os.path.join(root, fn)
943 if not fullpath in licfiles:
944 licfiles.append(fullpath)
945 for licfile in licfiles:
946 md5value = bb.utils.md5_file(licfile)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500947 license = md5sums.get(md5value, None)
948 if not license:
949 license, crunched_md5, lictext = crunch_license(licfile)
950 if not license:
951 license = 'Unknown'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500952 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
953
954 # FIXME should we grab at least one source file with a license header and add that too?
955
956 return licenses
957
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500958def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
959 """
960 Given a list of (license, path, md5sum) as returned by guess_license(),
961 a dict of package name to path mappings, write out a set of
962 package-specific LICENSE values.
963 """
964 pkglicenses = {pn: []}
965 for license, licpath, _ in licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600966 for pkgname, pkgpath in packages.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500967 if licpath.startswith(pkgpath + '/'):
968 if pkgname in pkglicenses:
969 pkglicenses[pkgname].append(license)
970 else:
971 pkglicenses[pkgname] = [license]
972 break
973 else:
974 # Accumulate on the main package
975 pkglicenses[pn].append(license)
976 outlicenses = {}
977 for pkgname in packages:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600978 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500979 if license == 'Unknown' and pkgname in fallback_licenses:
980 license = fallback_licenses[pkgname]
981 outlines.append('LICENSE_%s = "%s"' % (pkgname, license))
982 outlicenses[pkgname] = license.split()
983 return outlicenses
984
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500985def read_pkgconfig_provides(d):
986 pkgdatadir = d.getVar('PKGDATA_DIR', True)
987 pkgmap = {}
988 for fn in glob.glob(os.path.join(pkgdatadir, 'shlibs2', '*.pclist')):
989 with open(fn, 'r') as f:
990 for line in f:
991 pkgmap[os.path.basename(line.rstrip())] = os.path.splitext(os.path.basename(fn))[0]
992 recipemap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600993 for pc, pkg in pkgmap.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500994 pkgdatafile = os.path.join(pkgdatadir, 'runtime', pkg)
995 if os.path.exists(pkgdatafile):
996 with open(pkgdatafile, 'r') as f:
997 for line in f:
998 if line.startswith('PN: '):
999 recipemap[pc] = line.split(':', 1)[1].strip()
1000 return recipemap
1001
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001002def convert_debian(debpath):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001003 value_map = {'Package': 'PN',
1004 'Version': 'PV',
1005 'Section': 'SECTION',
1006 'License': 'LICENSE',
1007 'Homepage': 'HOMEPAGE'}
1008
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001009 # FIXME extend this mapping - perhaps use distro_alias.inc?
1010 depmap = {'libz-dev': 'zlib'}
1011
1012 values = {}
1013 depends = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001014 with open(os.path.join(debpath, 'control'), 'r', errors='surrogateescape') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001015 indesc = False
1016 for line in f:
1017 if indesc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001018 if line.startswith(' '):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001019 if line.startswith(' This package contains'):
1020 indesc = False
1021 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001022 if 'DESCRIPTION' in values:
1023 values['DESCRIPTION'] += ' ' + line.strip()
1024 else:
1025 values['DESCRIPTION'] = line.strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001026 else:
1027 indesc = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001028 if not indesc:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001029 splitline = line.split(':', 1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001030 if len(splitline) < 2:
1031 continue
1032 key = splitline[0]
1033 value = splitline[1].strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001034 if key == 'Build-Depends':
1035 for dep in value.split(','):
1036 dep = dep.split()[0]
1037 mapped = depmap.get(dep, '')
1038 if mapped:
1039 depends.append(mapped)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001040 elif key == 'Description':
1041 values['SUMMARY'] = value
1042 indesc = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001043 else:
1044 varname = value_map.get(key, None)
1045 if varname:
1046 values[varname] = value
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001047
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001048 #if depends:
1049 # values['DEPENDS'] = ' '.join(depends)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001050
1051 return values
1052
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001053def convert_rpm_xml(xmlfile):
1054 '''Converts the output from rpm -qp --xml to a set of variable values'''
1055 import xml.etree.ElementTree as ElementTree
1056 rpmtag_map = {'Name': 'PN',
1057 'Version': 'PV',
1058 'Summary': 'SUMMARY',
1059 'Description': 'DESCRIPTION',
1060 'License': 'LICENSE',
1061 'Url': 'HOMEPAGE'}
1062
1063 values = {}
1064 tree = ElementTree.parse(xmlfile)
1065 root = tree.getroot()
1066 for child in root:
1067 if child.tag == 'rpmTag':
1068 name = child.attrib.get('name', None)
1069 if name:
1070 varname = rpmtag_map.get(name, None)
1071 if varname:
1072 values[varname] = child[0].text
1073 return values
1074
1075
1076def check_npm(d):
1077 if not os.path.exists(os.path.join(d.getVar('STAGING_BINDIR_NATIVE', True), 'npm')):
1078 logger.error('npm required to process specified source, but npm is not available - you need to build nodejs-native first')
1079 sys.exit(14)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001080
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001081def register_commands(subparsers):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001082 parser_create = subparsers.add_parser('create',
1083 help='Create a new recipe',
1084 description='Creates a new recipe from a source tree')
1085 parser_create.add_argument('source', help='Path or URL to source')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001086 parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001087 parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
1088 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 -05001089 parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001090 parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001091 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 -05001092 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')
1093 parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001094 parser_create.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")
1095 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001096 parser_create.set_defaults(func=create_recipe)
1097