blob: 5bf939efcb26be456eaf9556061a4c5bfad9631c [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Recipe creation tool - create command plugin
2#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05003# Copyright (C) 2014-2017 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18import sys
19import os
20import argparse
21import glob
22import fnmatch
23import re
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050024import json
Patrick Williamsc124f4f2015-09-15 14:41:29 -050025import logging
26import scriptutils
Patrick Williamsc0f7c042017-02-23 20:41:17 -060027from urllib.parse import urlparse, urldefrag, urlsplit
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050028import hashlib
Brad Bishop6e60e8b2018-02-01 10:27:11 -050029import bb.fetch2
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030logger = logging.getLogger('recipetool')
31
32tinfoil = None
33plugins = None
34
Brad Bishop6e60e8b2018-02-01 10:27:11 -050035def log_error_cond(message, debugonly):
36 if debugonly:
37 logger.debug(message)
38 else:
39 logger.error(message)
40
41def log_info_cond(message, debugonly):
42 if debugonly:
43 logger.debug(message)
44 else:
45 logger.info(message)
46
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047def plugin_init(pluginlist):
48 # Take a reference to the list so we can use it later
49 global plugins
50 plugins = pluginlist
51
52def tinfoil_init(instance):
53 global tinfoil
54 tinfoil = instance
55
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050056class RecipeHandler(object):
57 recipelibmap = {}
58 recipeheadermap = {}
59 recipecmakefilemap = {}
60 recipebinmap = {}
61
Brad Bishop6e60e8b2018-02-01 10:27:11 -050062 def __init__(self):
63 self._devtool = False
64
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065 @staticmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050066 def load_libmap(d):
67 '''Load library->recipe mapping'''
68 import oe.package
69
70 if RecipeHandler.recipelibmap:
71 return
72 # First build up library->package mapping
73 shlib_providers = oe.package.read_shlib_providers(d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050074 libdir = d.getVar('libdir')
75 base_libdir = d.getVar('base_libdir')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050076 libpaths = list(set([base_libdir, libdir]))
77 libname_re = re.compile('^lib(.+)\.so.*$')
78 pkglibmap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -060079 for lib, item in shlib_providers.items():
80 for path, pkg in item.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050081 if path in libpaths:
82 res = libname_re.match(lib)
83 if res:
84 libname = res.group(1)
85 if not libname in pkglibmap:
86 pkglibmap[libname] = pkg[0]
87 else:
88 logger.debug('unable to extract library name from %s' % lib)
89
90 # Now turn it into a library->recipe mapping
Brad Bishop6e60e8b2018-02-01 10:27:11 -050091 pkgdata_dir = d.getVar('PKGDATA_DIR')
Patrick Williamsc0f7c042017-02-23 20:41:17 -060092 for libname, pkg in pkglibmap.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050093 try:
94 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
95 for line in f:
96 if line.startswith('PN:'):
97 RecipeHandler.recipelibmap[libname] = line.split(':', 1)[-1].strip()
98 break
99 except IOError as ioe:
100 if ioe.errno == 2:
101 logger.warn('unable to find a pkgdata file for package %s' % pkg)
102 else:
103 raise
104
105 # Some overrides - these should be mapped to the virtual
106 RecipeHandler.recipelibmap['GL'] = 'virtual/libgl'
107 RecipeHandler.recipelibmap['EGL'] = 'virtual/egl'
108 RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2'
109
110 @staticmethod
111 def load_devel_filemap(d):
112 '''Build up development file->recipe mapping'''
113 if RecipeHandler.recipeheadermap:
114 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500115 pkgdata_dir = d.getVar('PKGDATA_DIR')
116 includedir = d.getVar('includedir')
117 cmakedir = os.path.join(d.getVar('libdir'), 'cmake')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500118 for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')):
119 with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
120 pn = None
121 headers = []
122 cmakefiles = []
123 for line in f:
124 if line.startswith('PN:'):
125 pn = line.split(':', 1)[-1].strip()
126 elif line.startswith('FILES_INFO:'):
127 val = line.split(':', 1)[1].strip()
128 dictval = json.loads(val)
129 for fullpth in sorted(dictval):
130 if fullpth.startswith(includedir) and fullpth.endswith('.h'):
131 headers.append(os.path.relpath(fullpth, includedir))
132 elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'):
133 cmakefiles.append(os.path.relpath(fullpth, cmakedir))
134 if pn and headers:
135 for header in headers:
136 RecipeHandler.recipeheadermap[header] = pn
137 if pn and cmakefiles:
138 for fn in cmakefiles:
139 RecipeHandler.recipecmakefilemap[fn] = pn
140
141 @staticmethod
142 def load_binmap(d):
143 '''Build up native binary->recipe mapping'''
144 if RecipeHandler.recipebinmap:
145 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500146 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
147 staging_bindir_native = d.getVar('STAGING_BINDIR_NATIVE')
148 build_arch = d.getVar('BUILD_ARCH')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500149 fileprefix = 'manifest-%s-' % build_arch
150 for fn in glob.glob(os.path.join(sstate_manifests, '%s*-native.populate_sysroot' % fileprefix)):
151 with open(fn, 'r') as f:
152 pn = os.path.basename(fn).rsplit('.', 1)[0][len(fileprefix):]
153 for line in f:
154 if line.startswith(staging_bindir_native):
155 prog = os.path.basename(line.rstrip())
156 RecipeHandler.recipebinmap[prog] = pn
157
158 @staticmethod
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500159 def checkfiles(path, speclist, recursive=False, excludedirs=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160 results = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500161 if recursive:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500162 for root, dirs, files in os.walk(path, topdown=True):
163 if excludedirs:
164 dirs[:] = [d for d in dirs if d not in excludedirs]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500165 for fn in files:
166 for spec in speclist:
167 if fnmatch.fnmatch(fn, spec):
168 results.append(os.path.join(root, fn))
169 else:
170 for spec in speclist:
171 results.extend(glob.glob(os.path.join(path, spec)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500172 return results
173
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500174 @staticmethod
175 def handle_depends(libdeps, pcdeps, deps, outlines, values, d):
176 if pcdeps:
177 recipemap = read_pkgconfig_provides(d)
178 if libdeps:
179 RecipeHandler.load_libmap(d)
180
181 ignorelibs = ['socket']
182 ignoredeps = ['gcc-runtime', 'glibc', 'uclibc', 'musl', 'tar-native', 'binutils-native', 'coreutils-native']
183
184 unmappedpc = []
185 pcdeps = list(set(pcdeps))
186 for pcdep in pcdeps:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600187 if isinstance(pcdep, str):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500188 recipe = recipemap.get(pcdep, None)
189 if recipe:
190 deps.append(recipe)
191 else:
192 if not pcdep.startswith('$'):
193 unmappedpc.append(pcdep)
194 else:
195 for item in pcdep:
196 recipe = recipemap.get(pcdep, None)
197 if recipe:
198 deps.append(recipe)
199 break
200 else:
201 unmappedpc.append('(%s)' % ' or '.join(pcdep))
202
203 unmappedlibs = []
204 for libdep in libdeps:
205 if isinstance(libdep, tuple):
206 lib, header = libdep
207 else:
208 lib = libdep
209 header = None
210
211 if lib in ignorelibs:
212 logger.debug('Ignoring library dependency %s' % lib)
213 continue
214
215 recipe = RecipeHandler.recipelibmap.get(lib, None)
216 if recipe:
217 deps.append(recipe)
218 elif recipe is None:
219 if header:
220 RecipeHandler.load_devel_filemap(d)
221 recipe = RecipeHandler.recipeheadermap.get(header, None)
222 if recipe:
223 deps.append(recipe)
224 elif recipe is None:
225 unmappedlibs.append(lib)
226 else:
227 unmappedlibs.append(lib)
228
229 deps = set(deps).difference(set(ignoredeps))
230
231 if unmappedpc:
232 outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmappedpc))
233 outlines.append('# (this is based on recipes that have previously been built and packaged)')
234
235 if unmappedlibs:
236 outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmappedlibs))))
237 outlines.append('# (this is based on recipes that have previously been built and packaged)')
238
239 if deps:
240 values['DEPENDS'] = ' '.join(deps)
241
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500242 @staticmethod
243 def genfunction(outlines, funcname, content, python=False, forcespace=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500244 if python:
245 prefix = 'python '
246 else:
247 prefix = ''
248 outlines.append('%s%s () {' % (prefix, funcname))
249 if python or forcespace:
250 indent = ' '
251 else:
252 indent = '\t'
253 addnoop = not python
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 for line in content:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500255 outlines.append('%s%s' % (indent, line))
256 if addnoop:
257 strippedline = line.lstrip()
258 if strippedline and not strippedline.startswith('#'):
259 addnoop = False
260 if addnoop:
261 # Without this there'll be a syntax error
262 outlines.append('%s:' % indent)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263 outlines.append('}')
264 outlines.append('')
265
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500266 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267 return False
268
269
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500270def validate_pv(pv):
271 if not pv or '_version' in pv.lower() or pv[0] not in '0123456789':
272 return False
273 return True
274
275def determine_from_filename(srcfile):
276 """Determine name and version from a filename"""
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600277 if is_package(srcfile):
278 # Force getting the value from the package metadata
279 return None, None
280
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500281 if '.tar.' in srcfile:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600282 namepart = srcfile.split('.tar.')[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500283 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600284 namepart = os.path.splitext(srcfile)[0]
285 namepart = namepart.lower().replace('_', '-')
286 if namepart.endswith('.src'):
287 namepart = namepart[:-4]
288 if namepart.endswith('.orig'):
289 namepart = namepart[:-5]
290 splitval = namepart.split('-')
291 logger.debug('determine_from_filename: split name %s into: %s' % (srcfile, splitval))
292
293 ver_re = re.compile('^v?[0-9]')
294
295 pv = None
296 pn = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500297 if len(splitval) == 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600298 # Try to split the version out if there is no separator (or a .)
299 res = re.match('^([^0-9]+)([0-9.]+.*)$', namepart)
300 if res:
301 if len(res.group(1)) > 1 and len(res.group(2)) > 1:
302 pn = res.group(1).rstrip('.')
303 pv = res.group(2)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500304 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305 pn = namepart
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500306 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307 if splitval[-1] in ['source', 'src']:
308 splitval.pop()
309 if len(splitval) > 2 and re.match('^(alpha|beta|stable|release|rc[0-9]|pre[0-9]|p[0-9]|[0-9]{8})', splitval[-1]) and ver_re.match(splitval[-2]):
310 pv = '-'.join(splitval[-2:])
311 if pv.endswith('-release'):
312 pv = pv[:-8]
313 splitval = splitval[:-2]
314 elif ver_re.match(splitval[-1]):
315 pv = splitval.pop()
316 pn = '-'.join(splitval)
317 if pv and pv.startswith('v'):
318 pv = pv[1:]
319 logger.debug('determine_from_filename: name = "%s" version = "%s"' % (pn, pv))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500320 return (pn, pv)
321
322def determine_from_url(srcuri):
323 """Determine name and version from a URL"""
324 pn = None
325 pv = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600326 parseres = urlparse(srcuri.lower().split(';', 1)[0])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500327 if parseres.path:
328 if 'github.com' in parseres.netloc:
329 res = re.search(r'.*/(.*?)/archive/(.*)-final\.(tar|zip)', parseres.path)
330 if res:
331 pn = res.group(1).strip().replace('_', '-')
332 pv = res.group(2).strip().replace('_', '.')
333 else:
334 res = re.search(r'.*/(.*?)/archive/v?(.*)\.(tar|zip)', parseres.path)
335 if res:
336 pn = res.group(1).strip().replace('_', '-')
337 pv = res.group(2).strip().replace('_', '.')
338 elif 'bitbucket.org' in parseres.netloc:
339 res = re.search(r'.*/(.*?)/get/[a-zA-Z_-]*([0-9][0-9a-zA-Z_.]*)\.(tar|zip)', parseres.path)
340 if res:
341 pn = res.group(1).strip().replace('_', '-')
342 pv = res.group(2).strip().replace('_', '.')
343
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500344 if not pn and not pv:
345 if parseres.scheme not in ['git', 'gitsm', 'svn', 'hg']:
346 srcfile = os.path.basename(parseres.path.rstrip('/'))
347 pn, pv = determine_from_filename(srcfile)
348 elif parseres.scheme in ['git', 'gitsm']:
349 pn = os.path.basename(parseres.path.rstrip('/')).lower().replace('_', '-')
350 if pn.endswith('.git'):
351 pn = pn[:-4]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500352
353 logger.debug('Determined from source URL: name = "%s", version = "%s"' % (pn, pv))
354 return (pn, pv)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500355
356def supports_srcrev(uri):
357 localdata = bb.data.createCopy(tinfoil.config_data)
358 # This is a bit sad, but if you don't have this set there can be some
359 # odd interactions with the urldata cache which lead to errors
360 localdata.setVar('SRCREV', '${AUTOREV}')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600361 try:
362 fetcher = bb.fetch2.Fetch([uri], localdata)
363 urldata = fetcher.ud
364 for u in urldata:
365 if urldata[u].method.supports_srcrev():
366 return True
367 except bb.fetch2.FetchError as e:
368 logger.debug('FetchError in supports_srcrev: %s' % str(e))
369 # Fall back to basic check
370 if uri.startswith(('git://', 'gitsm://')):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500371 return True
372 return False
373
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500374def reformat_git_uri(uri):
375 '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
376 checkuri = uri.split(';', 1)[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600377 if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://github.com/[^/]+/[^/]+/?$', checkuri):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500378 # Appends scheme if the scheme is missing
379 if not '://' in uri:
380 uri = 'git://' + uri
381 scheme, host, path, user, pswd, parms = bb.fetch2.decodeurl(uri)
382 # Detection mechanism, this is required due to certain URL are formatter with ":" rather than "/"
383 # which causes decodeurl to fail getting the right host and path
384 if len(host.split(':')) > 1:
385 splitslash = host.split(':')
386 host = splitslash[0]
387 path = '/' + splitslash[1] + path
388 #Algorithm:
389 # if user is defined, append protocol=ssh or if a protocol is defined, then honor the user-defined protocol
390 # if no user & password is defined, check for scheme type and append the protocol with the scheme type
391 # finally if protocols or if the url is well-formed, do nothing and rejoin everything back to normal
392 # Need to repackage the arguments for encodeurl, the format is: (scheme, host, path, user, password, OrderedDict([('key', 'value')]))
393 if user:
394 if not 'protocol' in parms:
395 parms.update({('protocol', 'ssh')})
396 elif (scheme == "http" or scheme == 'https' or scheme == 'ssh') and not ('protocol' in parms):
397 parms.update({('protocol', scheme)})
398 # Always append 'git://'
399 fUrl = bb.fetch2.encodeurl(('git', host, path, user, pswd, parms))
400 return fUrl
401 else:
402 return uri
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500403
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600404def is_package(url):
405 '''Check if a URL points to a package'''
406 checkurl = url.split(';', 1)[0]
407 if checkurl.endswith(('.deb', '.ipk', '.rpm', '.srpm')):
408 return True
409 return False
410
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500411def create_recipe(args):
412 import bb.process
413 import tempfile
414 import shutil
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600415 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416
417 pkgarch = ""
418 if args.machine:
419 pkgarch = "${MACHINE_ARCH}"
420
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600421 extravalues = {}
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500422 checksums = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500423 tempsrc = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600424 source = args.source
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425 srcsubdir = ''
426 srcrev = '${AUTOREV}'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500427 srcbranch = ''
428 scheme = ''
429 storeTagName = ''
430 pv_srcpv = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600431
432 if os.path.isfile(source):
433 source = 'file://%s' % os.path.abspath(source)
434
435 if scriptutils.is_src_url(source):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500436 # Fetch a URL
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600437 fetchuri = reformat_git_uri(urldefrag(source)[0])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500438 if args.binary:
439 # Assume the archive contains the directory structure verbatim
440 # so we need to extract to a subdirectory
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600441 fetchuri += ';subdir=${BP}'
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500442 srcuri = fetchuri
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500443 rev_re = re.compile(';rev=([^;]+)')
444 res = rev_re.search(srcuri)
445 if res:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500446 if args.srcrev:
447 logger.error('rev= parameter and -S/--srcrev option cannot both be specified - use one or the other')
448 sys.exit(1)
449 if args.autorev:
450 logger.error('rev= parameter and -a/--autorev option cannot both be specified - use one or the other')
451 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452 srcrev = res.group(1)
453 srcuri = rev_re.sub('', srcuri)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500454 elif args.srcrev:
455 srcrev = args.srcrev
456
457 # Check whether users provides any branch info in fetchuri.
458 # If true, we will skip all branch checking process to honor all user's input.
459 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(fetchuri)
460 srcbranch = params.get('branch')
461 if args.srcbranch:
462 if srcbranch:
463 logger.error('branch= parameter and -B/--srcbranch option cannot both be specified - use one or the other')
464 sys.exit(1)
465 srcbranch = args.srcbranch
466 nobranch = params.get('nobranch')
467 if nobranch and srcbranch:
468 logger.error('nobranch= cannot be used if you specify a branch')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500469 sys.exit(1)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500470 tag = params.get('tag')
471 if not srcbranch and not nobranch and srcrev != '${AUTOREV}':
472 # Append nobranch=1 in the following conditions:
473 # 1. User did not set 'branch=' in srcuri, and
474 # 2. User did not set 'nobranch=1' in srcuri, and
475 # 3. Source revision is not '${AUTOREV}'
476 params['nobranch'] = '1'
477 if tag:
478 # Keep a copy of tag and append nobranch=1 then remove tag from URL.
479 # Bitbake fetcher unable to fetch when {AUTOREV} and tag is set at the same time.
480 storeTagName = params['tag']
481 params['nobranch'] = '1'
482 del params['tag']
483 if scheme == 'npm':
484 params['noverify'] = '1'
485 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
486
487 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
488 bb.utils.mkdirhier(tmpparent)
489 tempsrc = tempfile.mkdtemp(prefix='recipetool-', dir=tmpparent)
490 srctree = os.path.join(tempsrc, 'source')
491
492 try:
493 checksums, ftmpdir = scriptutils.fetch_url(tinfoil, fetchuri, srcrev, srctree, logger, preserve_tmp=args.keep_temp)
494 except scriptutils.FetchUrlFailure as e:
495 logger.error(str(e))
496 sys.exit(1)
497
498 if ftmpdir and args.keep_temp:
499 logger.info('Fetch temp directory is %s' % ftmpdir)
500
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 dirlist = os.listdir(srctree)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500502 filterout = ['git.indirectionsymlink']
503 dirlist = [x for x in dirlist if x not in filterout]
504 logger.debug('Directory listing (excluding filtered out):\n %s' % '\n '.join(dirlist))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500505 if len(dirlist) == 1:
506 singleitem = os.path.join(srctree, dirlist[0])
507 if os.path.isdir(singleitem):
508 # We unpacked a single directory, so we should use that
509 srcsubdir = dirlist[0]
510 srctree = os.path.join(srctree, srcsubdir)
511 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500512 check_single_file(dirlist[0], fetchuri)
513 elif len(dirlist) == 0:
514 if '/' in fetchuri:
515 fn = os.path.join(tinfoil.config_data.getVar('DL_DIR'), fetchuri.split('/')[-1])
516 if os.path.isfile(fn):
517 check_single_file(fn, fetchuri)
518 # If we've got to here then there's no source so we might as well give up
519 logger.error('URL %s resulted in an empty source tree' % fetchuri)
520 sys.exit(1)
521
522 # We need this checking mechanism to improve the recipe created by recipetool and devtool
523 # is able to parse and build by bitbake.
524 # If there is no input for branch name, then check for branch name with SRCREV provided.
525 if not srcbranch and not nobranch and srcrev and (srcrev != '${AUTOREV}') and scheme in ['git', 'gitsm']:
526 try:
527 cmd = 'git branch -r --contains'
528 check_branch, check_branch_err = bb.process.run('%s %s' % (cmd, srcrev), cwd=srctree)
529 except bb.process.ExecutionError as err:
530 logger.error(str(err))
531 sys.exit(1)
532 get_branch = [x.strip() for x in check_branch.splitlines()]
533 # Remove HEAD reference point and drop remote prefix
534 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
535 if 'master' in get_branch:
536 # If it is master, we do not need to append 'branch=master' as this is default.
537 # Even with the case where get_branch has multiple objects, if 'master' is one
538 # of them, we should default take from 'master'
539 srcbranch = ''
540 elif len(get_branch) == 1:
541 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
542 srcbranch = get_branch[0]
543 else:
544 # If get_branch contains more than one objects, then display error and exit.
545 mbrch = '\n ' + '\n '.join(get_branch)
546 logger.error('Revision %s was found on multiple branches: %s\nPlease provide the correct branch with -B/--srcbranch' % (srcrev, mbrch))
547 sys.exit(1)
548
549 # Since we might have a value in srcbranch, we need to
550 # recontruct the srcuri to include 'branch' in params.
551 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
552 if srcbranch:
553 params['branch'] = srcbranch
554
555 if storeTagName and scheme in ['git', 'gitsm']:
556 # Check srcrev using tag and check validity of the tag
557 cmd = ('git rev-parse --verify %s' % (storeTagName))
558 try:
559 check_tag, check_tag_err = bb.process.run('%s' % cmd, cwd=srctree)
560 srcrev = check_tag.split()[0]
561 except bb.process.ExecutionError as err:
562 logger.error(str(err))
563 logger.error("Possibly wrong tag name is provided")
564 sys.exit(1)
565 # Drop tag from srcuri as it will have conflicts with SRCREV during recipe parse.
566 del params['tag']
567 srcuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
568
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600569 if os.path.exists(os.path.join(srctree, '.gitmodules')) and srcuri.startswith('git://'):
570 srcuri = 'gitsm://' + srcuri[6:]
571 logger.info('Fetching submodules...')
572 bb.process.run('git submodule update --init --recursive', cwd=srctree)
573
574 if is_package(fetchuri):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500575 localdata = bb.data.createCopy(tinfoil.config_data)
576 pkgfile = bb.fetch2.localpath(fetchuri, localdata)
577 if pkgfile:
578 tmpfdir = tempfile.mkdtemp(prefix='recipetool-')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600579 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600580 if pkgfile.endswith(('.deb', '.ipk')):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500581 stdout, _ = bb.process.run('ar x %s' % pkgfile, cwd=tmpfdir)
582 stdout, _ = bb.process.run('tar xf control.tar.gz', cwd=tmpfdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600583 values = convert_debian(tmpfdir)
584 extravalues.update(values)
585 elif pkgfile.endswith(('.rpm', '.srpm')):
586 stdout, _ = bb.process.run('rpm -qp --xml %s > pkginfo.xml' % pkgfile, cwd=tmpfdir)
587 values = convert_rpm_xml(os.path.join(tmpfdir, 'pkginfo.xml'))
588 extravalues.update(values)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500589 finally:
590 shutil.rmtree(tmpfdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591 else:
592 # Assume we're pointing to an existing source tree
593 if args.extract_to:
594 logger.error('--extract-to cannot be specified if source is a directory')
595 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600596 if not os.path.isdir(source):
597 logger.error('Invalid source directory %s' % source)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500598 sys.exit(1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600599 srctree = source
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500600 srcuri = ''
601 if os.path.exists(os.path.join(srctree, '.git')):
602 # Try to get upstream repo location from origin remote
603 try:
604 stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
605 except bb.process.ExecutionError as e:
606 stdout = None
607 if stdout:
608 for line in stdout.splitlines():
609 splitline = line.split()
610 if len(splitline) > 1:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600611 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500612 srcuri = reformat_git_uri(splitline[1])
613 srcsubdir = 'git'
614 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500616 if args.src_subdir:
617 srcsubdir = os.path.join(srcsubdir, args.src_subdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500618 srctree_use = os.path.abspath(os.path.join(srctree, args.src_subdir))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500619 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500620 srctree_use = os.path.abspath(srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500621
622 if args.outfile and os.path.isdir(args.outfile):
623 outfile = None
624 outdir = args.outfile
625 else:
626 outfile = args.outfile
627 outdir = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500628 if outfile and outfile != '-':
629 if os.path.exists(outfile):
630 logger.error('Output file %s already exists' % outfile)
631 sys.exit(1)
632
633 lines_before = []
634 lines_after = []
635
636 lines_before.append('# Recipe created by %s' % os.path.basename(sys.argv[0]))
637 lines_before.append('# This is the basis of a recipe and may need further editing in order to be fully functional.')
638 lines_before.append('# (Feel free to remove these comments when editing.)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600639 # We need a blank line here so that patch_recipe_lines can rewind before the LICENSE comments
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500640 lines_before.append('')
641
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500642 # We'll come back and replace this later in handle_license_vars()
643 lines_before.append('##LICENSE_PLACEHOLDER##')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600644
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500645 handled = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500646 classes = []
647
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500648 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500649 pn = None
650 pv = None
651 if outfile:
652 recipefn = os.path.splitext(os.path.basename(outfile))[0]
653 fnsplit = recipefn.split('_')
654 if len(fnsplit) > 1:
655 pn = fnsplit[0]
656 pv = fnsplit[1]
657 else:
658 pn = recipefn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500659
660 if args.version:
661 pv = args.version
662
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500663 if args.name:
664 pn = args.name
665 if args.name.endswith('-native'):
666 if args.also_native:
667 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')
668 sys.exit(1)
669 classes.append('native')
670 elif args.name.startswith('nativesdk-'):
671 if args.also_native:
672 logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
673 sys.exit(1)
674 classes.append('nativesdk')
675
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500676 if pv and pv not in 'git svn hg'.split():
677 realpv = pv
678 else:
679 realpv = None
680
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500681 if not srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500682 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
683 lines_before.append('SRC_URI = "%s"' % srcuri)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500684 for key, value in sorted(checksums.items()):
685 lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500686 if srcuri and supports_srcrev(srcuri):
687 lines_before.append('')
688 lines_before.append('# Modify these as desired')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500689 # Note: we have code to replace realpv further down if it gets set to some other value
690 scheme, _, _, _, _, _ = bb.fetch2.decodeurl(srcuri)
691 if scheme in ['git', 'gitsm']:
692 srcpvprefix = 'git'
693 elif scheme == 'svn':
694 srcpvprefix = 'svnr'
695 else:
696 srcpvprefix = scheme
697 lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix))
698 pv_srcpv = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600699 if not args.autorev and srcrev == '${AUTOREV}':
700 if os.path.exists(os.path.join(srctree, '.git')):
701 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
702 srcrev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500703 lines_before.append('SRCREV = "%s"' % srcrev)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500704 if args.provides:
705 lines_before.append('PROVIDES = "%s"' % args.provides)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500706 lines_before.append('')
707
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600708 if srcsubdir and not args.binary:
709 # (for binary packages we explicitly specify subdir= when fetching to
710 # match the default value of S, so we don't need to set it in that case)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711 lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
712 lines_before.append('')
713
714 if pkgarch:
715 lines_after.append('PACKAGE_ARCH = "%s"' % pkgarch)
716 lines_after.append('')
717
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500718 if args.binary:
719 lines_after.append('INSANE_SKIP_${PN} += "already-stripped"')
720 lines_after.append('')
721
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500722 if args.fetch_dev:
723 extravalues['fetchdev'] = True
724 else:
725 extravalues['fetchdev'] = None
726
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500727 # Find all plugins that want to register handlers
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500728 logger.debug('Loading recipe handlers')
729 raw_handlers = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500730 for plugin in plugins:
731 if hasattr(plugin, 'register_recipe_handlers'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500732 plugin.register_recipe_handlers(raw_handlers)
733 # Sort handlers by priority
734 handlers = []
735 for i, handler in enumerate(raw_handlers):
736 if isinstance(handler, tuple):
737 handlers.append((handler[0], handler[1], i))
738 else:
739 handlers.append((handler, 0, i))
740 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
741 for handler, priority, _ in handlers:
742 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500743 setattr(handler, '_devtool', args.devtool)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500744 handlers = [item[0] for item in handlers]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500745
746 # Apply the handlers
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500747 if args.binary:
748 classes.append('bin_package')
749 handled.append('buildsystem')
750
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 for handler in handlers:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500752 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
753
754 extrafiles = extravalues.pop('extrafiles', {})
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600755 extra_pn = extravalues.pop('PN', None)
756 extra_pv = extravalues.pop('PV', None)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500757
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600758 if extra_pv and not realpv:
759 realpv = extra_pv
760 if not validate_pv(realpv):
761 realpv = None
762 else:
763 realpv = realpv.lower().split()[0]
764 if '_' in realpv:
765 realpv = realpv.replace('_', '-')
766 if extra_pn and not pn:
767 pn = extra_pn
768 if pn.startswith('GNU '):
769 pn = pn[4:]
770 if ' ' in pn:
771 # Probably a descriptive identifier rather than a proper name
772 pn = None
773 else:
774 pn = pn.lower()
775 if '_' in pn:
776 pn = pn.replace('_', '-')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500777
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500778 if srcuri and not realpv or not pn:
779 name_pn, name_pv = determine_from_url(srcuri)
780 if name_pn and not pn:
781 pn = name_pn
782 if name_pv and not realpv:
783 realpv = name_pv
784
785 licvalues = handle_license_vars(srctree_use, lines_before, handled, extravalues, tinfoil.config_data)
786
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500787 if not outfile:
788 if not pn:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500789 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 -0500790 # devtool looks for this specific exit code, so don't change it
791 sys.exit(15)
792 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600793 if srcuri and srcuri.startswith(('gitsm://', 'git://', 'hg://', 'svn://')):
794 suffix = srcuri.split(':', 1)[0]
795 if suffix == 'gitsm':
796 suffix = 'git'
797 outfile = '%s_%s.bb' % (pn, suffix)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500798 elif realpv:
799 outfile = '%s_%s.bb' % (pn, realpv)
800 else:
801 outfile = '%s.bb' % pn
802 if outdir:
803 outfile = os.path.join(outdir, outfile)
804 # We need to check this again
805 if os.path.exists(outfile):
806 logger.error('Output file %s already exists' % outfile)
807 sys.exit(1)
808
809 # Move any extra files the plugins created to a directory next to the recipe
810 if extrafiles:
811 if outfile == '-':
812 extraoutdir = pn
813 else:
814 extraoutdir = os.path.join(os.path.dirname(outfile), pn)
815 bb.utils.mkdirhier(extraoutdir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600816 for destfn, extrafile in extrafiles.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500817 shutil.move(extrafile, os.path.join(extraoutdir, destfn))
818
819 lines = lines_before
820 lines_before = []
821 skipblank = True
822 for line in lines:
823 if skipblank:
824 skipblank = False
825 if not line:
826 continue
827 if line.startswith('S = '):
828 if realpv and pv not in 'git svn hg'.split():
829 line = line.replace(realpv, '${PV}')
830 if pn:
831 line = line.replace(pn, '${BPN}')
832 if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
833 skipblank = True
834 continue
835 elif line.startswith('SRC_URI = '):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500836 if realpv and not pv_srcpv:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500837 line = line.replace(realpv, '${PV}')
838 elif line.startswith('PV = '):
839 if realpv:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500840 # Replace the first part of the PV value
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500841 line = re.sub('"[^+]*\+', '"%s+' % realpv, line)
842 lines_before.append(line)
843
844 if args.also_native:
845 lines = lines_after
846 lines_after = []
847 bbclassextend = None
848 for line in lines:
849 if line.startswith('BBCLASSEXTEND ='):
850 splitval = line.split('"')
851 if len(splitval) > 1:
852 bbclassextend = splitval[1].split()
853 if not 'native' in bbclassextend:
854 bbclassextend.insert(0, 'native')
855 line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
856 lines_after.append(line)
857 if not bbclassextend:
858 lines_after.append('BBCLASSEXTEND = "native"')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500859
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500860 postinst = ("postinst", extravalues.pop('postinst', None))
861 postrm = ("postrm", extravalues.pop('postrm', None))
862 preinst = ("preinst", extravalues.pop('preinst', None))
863 prerm = ("prerm", extravalues.pop('prerm', None))
864 funcs = [postinst, postrm, preinst, prerm]
865 for func in funcs:
866 if func[1]:
867 RecipeHandler.genfunction(lines_after, 'pkg_%s_${PN}' % func[0], func[1])
868
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500869 outlines = []
870 outlines.extend(lines_before)
871 if classes:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500872 if outlines[-1] and not outlines[-1].startswith('#'):
873 outlines.append('')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874 outlines.append('inherit %s' % ' '.join(classes))
875 outlines.append('')
876 outlines.extend(lines_after)
877
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600878 if extravalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600879 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False)
880
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500881 if args.extract_to:
882 scriptutils.git_convert_standalone_clone(srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500883 if os.path.isdir(args.extract_to):
884 # If the directory exists we'll move the temp dir into it instead of
885 # its contents - of course, we could try to always move its contents
886 # but that is a pain if there are symlinks; the simplest solution is
887 # to just remove it first
888 os.rmdir(args.extract_to)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500889 shutil.move(srctree, args.extract_to)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500890 if tempsrc == srctree:
891 tempsrc = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500892 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500893
894 if outfile == '-':
895 sys.stdout.write('\n'.join(outlines) + '\n')
896 else:
897 with open(outfile, 'w') as f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600898 lastline = None
899 for line in outlines:
900 if not lastline and not line:
901 # Skip extra blank lines
902 continue
903 f.write('%s\n' % line)
904 lastline = line
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500905 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 -0500906
907 if tempsrc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600908 if args.keep_temp:
909 logger.info('Preserving temporary directory %s' % tempsrc)
910 else:
911 shutil.rmtree(tempsrc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500912
913 return 0
914
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500915def check_single_file(fn, fetchuri):
916 """Determine if a single downloaded file is something we can't handle"""
917 with open(fn, 'r', errors='surrogateescape') as f:
918 if '<html' in f.read(100).lower():
919 logger.error('Fetching "%s" returned a single HTML page - check the URL is correct and functional' % fetchuri)
920 sys.exit(1)
921
922def split_value(value):
923 if isinstance(value, str):
924 return value.split()
925 else:
926 return value
927
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600928def handle_license_vars(srctree, lines_before, handled, extravalues, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500929 lichandled = [x for x in handled if x[0] == 'license']
930 if lichandled:
931 # Someone else has already handled the license vars, just return their value
932 return lichandled[0][1]
933
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600934 licvalues = guess_license(srctree, d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500935 licenses = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600936 lic_files_chksum = []
937 lic_unknown = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500938 lines = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600939 if licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600940 for licvalue in licvalues:
941 if not licvalue[0] in licenses:
942 licenses.append(licvalue[0])
943 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
944 if licvalue[0] == 'Unknown':
945 lic_unknown.append(licvalue[1])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600946 if lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500947 lines.append('#')
948 lines.append('# The following license files were not able to be identified and are')
949 lines.append('# represented as "Unknown" below, you will need to check them yourself:')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600950 for licfile in lic_unknown:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500951 lines.append('# %s' % licfile)
952
953 extra_license = split_value(extravalues.pop('LICENSE', []))
954 if '&' in extra_license:
955 extra_license.remove('&')
956 if extra_license:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600957 if licenses == ['Unknown']:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500958 licenses = extra_license
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600959 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500960 for item in extra_license:
961 if item not in licenses:
962 licenses.append(item)
963 extra_lic_files_chksum = split_value(extravalues.pop('LIC_FILES_CHKSUM', []))
964 for item in extra_lic_files_chksum:
965 if item not in lic_files_chksum:
966 lic_files_chksum.append(item)
967
968 if lic_files_chksum:
969 # We are going to set the vars, so prepend the standard disclaimer
970 lines.insert(0, '# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is')
971 lines.insert(1, '# your responsibility to verify that the values are complete and correct.')
972 else:
973 # Without LIC_FILES_CHKSUM we set LICENSE = "CLOSED" to allow the
974 # user to get started easily
975 lines.append('# Unable to find any files that looked like license statements. Check the accompanying')
976 lines.append('# documentation and source headers and set LICENSE and LIC_FILES_CHKSUM accordingly.')
977 lines.append('#')
978 lines.append('# NOTE: LICENSE is being set to "CLOSED" to allow you to at least start building - if')
979 lines.append('# this is not accurate with respect to the licensing of the software being built (it')
980 lines.append('# will not be in most cases) you must specify the correct value before using this')
981 lines.append('# recipe for anything other than initial testing/development!')
982 licenses = ['CLOSED']
983
984 if extra_license and sorted(licenses) != sorted(extra_license):
985 lines.append('# NOTE: Original package / source metadata indicates license is: %s' % ' & '.join(extra_license))
986
987 if len(licenses) > 1:
988 lines.append('#')
989 lines.append('# NOTE: multiple licenses have been detected; they have been separated with &')
990 lines.append('# in the LICENSE value for now since it is a reasonable assumption that all')
991 lines.append('# of the licenses apply. If instead there is a choice between the multiple')
992 lines.append('# licenses then you should change the value to separate the licenses with |')
993 lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
994 lines.append('# to determine which situation is applicable.')
995
996 lines.append('LICENSE = "%s"' % ' & '.join(licenses))
997 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
998 lines.append('')
999
1000 # Replace the placeholder so we get the values in the right place in the recipe file
1001 try:
1002 pos = lines_before.index('##LICENSE_PLACEHOLDER##')
1003 except ValueError:
1004 pos = -1
1005 if pos == -1:
1006 lines_before.extend(lines)
1007 else:
1008 lines_before[pos:pos+1] = lines
1009
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001010 handled.append(('license', licvalues))
1011 return licvalues
1012
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001013def get_license_md5sums(d, static_only=False):
1014 import bb.utils
1015 md5sums = {}
1016 if not static_only:
1017 # Gather md5sums of license files in common license dir
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001018 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001019 for fn in os.listdir(commonlicdir):
1020 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1021 md5sums[md5value] = fn
1022 # The following were extracted from common values in various recipes
1023 # (double checking the license against the license file itself, not just
1024 # the LICENSE value in the recipe)
1025 md5sums['94d55d512a9ba36caa9b7df079bae19f'] = 'GPLv2'
1026 md5sums['b234ee4d69f5fce4486a80fdaf4a4263'] = 'GPLv2'
1027 md5sums['59530bdf33659b29e73d4adb9f9f6552'] = 'GPLv2'
1028 md5sums['0636e73ff0215e8d672dc4c32c317bb3'] = 'GPLv2'
1029 md5sums['eb723b61539feef013de476e68b5c50a'] = 'GPLv2'
1030 md5sums['751419260aa954499f7abaabaa882bbe'] = 'GPLv2'
1031 md5sums['393a5ca445f6965873eca0259a17f833'] = 'GPLv2'
1032 md5sums['12f884d2ae1ff87c09e5b7ccc2c4ca7e'] = 'GPLv2'
1033 md5sums['8ca43cbc842c2336e835926c2166c28b'] = 'GPLv2'
1034 md5sums['ebb5c50ab7cab4baeffba14977030c07'] = 'GPLv2'
1035 md5sums['c93c0550bd3173f4504b2cbd8991e50b'] = 'GPLv2'
1036 md5sums['9ac2e7cff1ddaf48b6eab6028f23ef88'] = 'GPLv2'
1037 md5sums['4325afd396febcb659c36b49533135d4'] = 'GPLv2'
1038 md5sums['18810669f13b87348459e611d31ab760'] = 'GPLv2'
1039 md5sums['d7810fab7487fb0aad327b76f1be7cd7'] = 'GPLv2' # the Linux kernel's COPYING file
1040 md5sums['bbb461211a33b134d42ed5ee802b37ff'] = 'LGPLv2.1'
1041 md5sums['7fbc338309ac38fefcd64b04bb903e34'] = 'LGPLv2.1'
1042 md5sums['4fbd65380cdd255951079008b364516c'] = 'LGPLv2.1'
1043 md5sums['2d5025d4aa3495befef8f17206a5b0a1'] = 'LGPLv2.1'
1044 md5sums['fbc093901857fcd118f065f900982c24'] = 'LGPLv2.1'
1045 md5sums['a6f89e2100d9b6cdffcea4f398e37343'] = 'LGPLv2.1'
1046 md5sums['d8045f3b8f929c1cb29a1e3fd737b499'] = 'LGPLv2.1'
1047 md5sums['fad9b3332be894bab9bc501572864b29'] = 'LGPLv2.1'
1048 md5sums['3bf50002aefd002f49e7bb854063f7e7'] = 'LGPLv2'
1049 md5sums['9f604d8a4f8e74f4f5140845a21b6674'] = 'LGPLv2'
1050 md5sums['5f30f0716dfdd0d91eb439ebec522ec2'] = 'LGPLv2'
1051 md5sums['55ca817ccb7d5b5b66355690e9abc605'] = 'LGPLv2'
1052 md5sums['252890d9eee26aab7b432e8b8a616475'] = 'LGPLv2'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001053 md5sums['3214f080875748938ba060314b4f727d'] = 'LGPLv2'
1054 md5sums['db979804f025cf55aabec7129cb671ed'] = 'LGPLv2'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001055 md5sums['d32239bcb673463ab874e80d47fae504'] = 'GPLv3'
1056 md5sums['f27defe1e96c2e1ecd4e0c9be8967949'] = 'GPLv3'
1057 md5sums['6a6a8e020838b23406c81b19c1d46df6'] = 'LGPLv3'
1058 md5sums['3b83ef96387f14655fc854ddc3c6bd57'] = 'Apache-2.0'
1059 md5sums['385c55653886acac3821999a3ccd17b3'] = 'Artistic-1.0 | GPL-2.0' # some perl modules
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001060 md5sums['54c7042be62e169199200bc6477f04d1'] = 'BSD-3-Clause'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001061 return md5sums
1062
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001063def crunch_license(licfile):
1064 '''
1065 Remove non-material text from a license file and then check
1066 its md5sum against a known list. This works well for licenses
1067 which contain a copyright statement, but is also a useful way
1068 to handle people's insistence upon reformatting the license text
1069 slightly (with no material difference to the text of the
1070 license).
1071 '''
1072
1073 import oe.utils
1074
1075 # Note: these are carefully constructed!
1076 license_title_re = re.compile('^\(?(#+ *)?(The )?.{1,10} [Ll]icen[sc]e( \(.{1,10}\))?\)?:?$')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001077 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 -05001078 copyright_re = re.compile('^(#+)? *Copyright .*$')
1079
1080 crunched_md5sums = {}
1081 # The following two were gleaned from the "forever" npm package
1082 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
1083 crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
1084 # https://github.com/vasi/pixz/blob/master/LICENSE
1085 crunched_md5sums['2f03392b40bbe663597b5bd3cc5ebdb9'] = 'BSD-2-Clause'
1086 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
1087 crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause'
1088 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
1089 crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2'
1090 # https://github.com/datto/dattobd/blob/master/COPYING
1091 # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT
1092 crunched_md5sums['1d65c5ad4bf6489f85f4812bf08ae73d'] = 'GPLv2'
1093 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
1094 # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD
1095 crunched_md5sums['fb530f66a7a89ce920f0e912b5b66d4b'] = 'GPLv2'
1096 # https://github.com/gkos/nrf24/blob/master/COPYING
1097 crunched_md5sums['7b6aaa4daeafdfa6ed5443fd2684581b'] = 'GPLv2'
1098 # https://github.com/josch09/resetusb/blob/master/COPYING
1099 crunched_md5sums['8b8ac1d631a4d220342e83bcf1a1fbc3'] = 'GPLv3'
1100 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
1101 crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1'
1102 # unixODBC-2.3.4 COPYING
1103 crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1'
1104 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
1105 crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3'
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001106 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1107 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
1108 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/edl-v10
1109 crunched_md5sums['0a9c78c0a398d1bbce4a166757d60387'] = 'EDL-1.0'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001110 lictext = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001111 with open(licfile, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001112 for line in f:
1113 # Drop opening statements
1114 if copyright_re.match(line):
1115 continue
1116 elif license_title_re.match(line):
1117 continue
1118 elif license_statement_re.match(line):
1119 continue
1120 # Squash spaces, and replace smart quotes, double quotes
1121 # and backticks with single quotes
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001122 line = oe.utils.squashspaces(line.strip())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001123 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
1124 if line:
1125 lictext.append(line)
1126
1127 m = hashlib.md5()
1128 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001129 m.update(' '.join(lictext).encode('utf-8'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001130 md5val = m.hexdigest()
1131 except UnicodeEncodeError:
1132 md5val = None
1133 lictext = ''
1134 license = crunched_md5sums.get(md5val, None)
1135 return license, md5val, lictext
1136
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001137def guess_license(srctree, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001138 import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001139 md5sums = get_license_md5sums(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001140
1141 licenses = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001142 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001143 licfiles = []
1144 for root, dirs, files in os.walk(srctree):
1145 for fn in files:
1146 for spec in licspecs:
1147 if fnmatch.fnmatch(fn, spec):
1148 fullpath = os.path.join(root, fn)
1149 if not fullpath in licfiles:
1150 licfiles.append(fullpath)
1151 for licfile in licfiles:
1152 md5value = bb.utils.md5_file(licfile)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001153 license = md5sums.get(md5value, None)
1154 if not license:
1155 license, crunched_md5, lictext = crunch_license(licfile)
1156 if not license:
1157 license = 'Unknown'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001158 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
1159
1160 # FIXME should we grab at least one source file with a license header and add that too?
1161
1162 return licenses
1163
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001164def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
1165 """
1166 Given a list of (license, path, md5sum) as returned by guess_license(),
1167 a dict of package name to path mappings, write out a set of
1168 package-specific LICENSE values.
1169 """
1170 pkglicenses = {pn: []}
1171 for license, licpath, _ in licvalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001172 for pkgname, pkgpath in packages.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001173 if licpath.startswith(pkgpath + '/'):
1174 if pkgname in pkglicenses:
1175 pkglicenses[pkgname].append(license)
1176 else:
1177 pkglicenses[pkgname] = [license]
1178 break
1179 else:
1180 # Accumulate on the main package
1181 pkglicenses[pn].append(license)
1182 outlicenses = {}
1183 for pkgname in packages:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001184 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001185 if license == 'Unknown' and pkgname in fallback_licenses:
1186 license = fallback_licenses[pkgname]
1187 outlines.append('LICENSE_%s = "%s"' % (pkgname, license))
1188 outlicenses[pkgname] = license.split()
1189 return outlicenses
1190
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001191def read_pkgconfig_provides(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001192 pkgdatadir = d.getVar('PKGDATA_DIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001193 pkgmap = {}
1194 for fn in glob.glob(os.path.join(pkgdatadir, 'shlibs2', '*.pclist')):
1195 with open(fn, 'r') as f:
1196 for line in f:
1197 pkgmap[os.path.basename(line.rstrip())] = os.path.splitext(os.path.basename(fn))[0]
1198 recipemap = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001199 for pc, pkg in pkgmap.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001200 pkgdatafile = os.path.join(pkgdatadir, 'runtime', pkg)
1201 if os.path.exists(pkgdatafile):
1202 with open(pkgdatafile, 'r') as f:
1203 for line in f:
1204 if line.startswith('PN: '):
1205 recipemap[pc] = line.split(':', 1)[1].strip()
1206 return recipemap
1207
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001208def convert_debian(debpath):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001209 value_map = {'Package': 'PN',
1210 'Version': 'PV',
1211 'Section': 'SECTION',
1212 'License': 'LICENSE',
1213 'Homepage': 'HOMEPAGE'}
1214
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001215 # FIXME extend this mapping - perhaps use distro_alias.inc?
1216 depmap = {'libz-dev': 'zlib'}
1217
1218 values = {}
1219 depends = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001220 with open(os.path.join(debpath, 'control'), 'r', errors='surrogateescape') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001221 indesc = False
1222 for line in f:
1223 if indesc:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001224 if line.startswith(' '):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001225 if line.startswith(' This package contains'):
1226 indesc = False
1227 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001228 if 'DESCRIPTION' in values:
1229 values['DESCRIPTION'] += ' ' + line.strip()
1230 else:
1231 values['DESCRIPTION'] = line.strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001232 else:
1233 indesc = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001234 if not indesc:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001235 splitline = line.split(':', 1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001236 if len(splitline) < 2:
1237 continue
1238 key = splitline[0]
1239 value = splitline[1].strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001240 if key == 'Build-Depends':
1241 for dep in value.split(','):
1242 dep = dep.split()[0]
1243 mapped = depmap.get(dep, '')
1244 if mapped:
1245 depends.append(mapped)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001246 elif key == 'Description':
1247 values['SUMMARY'] = value
1248 indesc = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001249 else:
1250 varname = value_map.get(key, None)
1251 if varname:
1252 values[varname] = value
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001253 postinst = os.path.join(debpath, 'postinst')
1254 postrm = os.path.join(debpath, 'postrm')
1255 preinst = os.path.join(debpath, 'preinst')
1256 prerm = os.path.join(debpath, 'prerm')
1257 sfiles = [postinst, postrm, preinst, prerm]
1258 for sfile in sfiles:
1259 if os.path.isfile(sfile):
1260 logger.info("Converting %s file to recipe function..." %
1261 os.path.basename(sfile).upper())
1262 content = []
1263 with open(sfile) as f:
1264 for line in f:
1265 if "#!/" in line:
1266 continue
1267 line = line.rstrip("\n")
1268 if line.strip():
1269 content.append(line)
1270 if content:
1271 values[os.path.basename(f.name)] = content
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001272
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001273 #if depends:
1274 # values['DEPENDS'] = ' '.join(depends)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001275
1276 return values
1277
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001278def convert_rpm_xml(xmlfile):
1279 '''Converts the output from rpm -qp --xml to a set of variable values'''
1280 import xml.etree.ElementTree as ElementTree
1281 rpmtag_map = {'Name': 'PN',
1282 'Version': 'PV',
1283 'Summary': 'SUMMARY',
1284 'Description': 'DESCRIPTION',
1285 'License': 'LICENSE',
1286 'Url': 'HOMEPAGE'}
1287
1288 values = {}
1289 tree = ElementTree.parse(xmlfile)
1290 root = tree.getroot()
1291 for child in root:
1292 if child.tag == 'rpmTag':
1293 name = child.attrib.get('name', None)
1294 if name:
1295 varname = rpmtag_map.get(name, None)
1296 if varname:
1297 values[varname] = child[0].text
1298 return values
1299
1300
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001301def register_commands(subparsers):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001302 parser_create = subparsers.add_parser('create',
1303 help='Create a new recipe',
1304 description='Creates a new recipe from a source tree')
1305 parser_create.add_argument('source', help='Path or URL to source')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001306 parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001307 parser_create.add_argument('-p', '--provides', help='Specify an alias for the item provided by the recipe')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001308 parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
1309 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 -05001310 parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001311 parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001312 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 -05001313 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')
1314 parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001315 group = parser_create.add_mutually_exclusive_group()
1316 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")
1317 group.add_argument('-S', '--srcrev', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
1318 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 -06001319 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001320 parser_create.add_argument('--fetch-dev', action="store_true", help='For npm, also fetch devDependencies')
1321 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001322 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1323 parser_create.set_defaults(func=create_recipe)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001324