create.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329
  1. # Recipe creation tool - create command plugin
  2. #
  3. # Copyright (C) 2014-2017 Intel Corporation
  4. #
  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. import sys
  18. import os
  19. import argparse
  20. import glob
  21. import fnmatch
  22. import re
  23. import json
  24. import logging
  25. import scriptutils
  26. from urllib.parse import urlparse, urldefrag, urlsplit
  27. import hashlib
  28. import bb.fetch2
  29. logger = logging.getLogger('recipetool')
  30. tinfoil = None
  31. plugins = None
  32. def log_error_cond(message, debugonly):
  33. if debugonly:
  34. logger.debug(message)
  35. else:
  36. logger.error(message)
  37. def log_info_cond(message, debugonly):
  38. if debugonly:
  39. logger.debug(message)
  40. else:
  41. logger.info(message)
  42. def plugin_init(pluginlist):
  43. # Take a reference to the list so we can use it later
  44. global plugins
  45. plugins = pluginlist
  46. def tinfoil_init(instance):
  47. global tinfoil
  48. tinfoil = instance
  49. class RecipeHandler(object):
  50. recipelibmap = {}
  51. recipeheadermap = {}
  52. recipecmakefilemap = {}
  53. recipebinmap = {}
  54. def __init__(self):
  55. self._devtool = False
  56. @staticmethod
  57. def load_libmap(d):
  58. '''Load library->recipe mapping'''
  59. import oe.package
  60. if RecipeHandler.recipelibmap:
  61. return
  62. # First build up library->package mapping
  63. shlib_providers = oe.package.read_shlib_providers(d)
  64. libdir = d.getVar('libdir')
  65. base_libdir = d.getVar('base_libdir')
  66. libpaths = list(set([base_libdir, libdir]))
  67. libname_re = re.compile('^lib(.+)\.so.*$')
  68. pkglibmap = {}
  69. for lib, item in shlib_providers.items():
  70. for path, pkg in item.items():
  71. if path in libpaths:
  72. res = libname_re.match(lib)
  73. if res:
  74. libname = res.group(1)
  75. if not libname in pkglibmap:
  76. pkglibmap[libname] = pkg[0]
  77. else:
  78. logger.debug('unable to extract library name from %s' % lib)
  79. # Now turn it into a library->recipe mapping
  80. pkgdata_dir = d.getVar('PKGDATA_DIR')
  81. for libname, pkg in pkglibmap.items():
  82. try:
  83. with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
  84. for line in f:
  85. if line.startswith('PN:'):
  86. RecipeHandler.recipelibmap[libname] = line.split(':', 1)[-1].strip()
  87. break
  88. except IOError as ioe:
  89. if ioe.errno == 2:
  90. logger.warning('unable to find a pkgdata file for package %s' % pkg)
  91. else:
  92. raise
  93. # Some overrides - these should be mapped to the virtual
  94. RecipeHandler.recipelibmap['GL'] = 'virtual/libgl'
  95. RecipeHandler.recipelibmap['EGL'] = 'virtual/egl'
  96. RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2'
  97. @staticmethod
  98. def load_devel_filemap(d):
  99. '''Build up development file->recipe mapping'''
  100. if RecipeHandler.recipeheadermap:
  101. return
  102. pkgdata_dir = d.getVar('PKGDATA_DIR')
  103. includedir = d.getVar('includedir')
  104. cmakedir = os.path.join(d.getVar('libdir'), 'cmake')
  105. for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')):
  106. with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
  107. pn = None
  108. headers = []
  109. cmakefiles = []
  110. for line in f:
  111. if line.startswith('PN:'):
  112. pn = line.split(':', 1)[-1].strip()
  113. elif line.startswith('FILES_INFO:'):
  114. val = line.split(':', 1)[1].strip()
  115. dictval = json.loads(val)
  116. for fullpth in sorted(dictval):
  117. if fullpth.startswith(includedir) and fullpth.endswith('.h'):
  118. headers.append(os.path.relpath(fullpth, includedir))
  119. elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'):
  120. cmakefiles.append(os.path.relpath(fullpth, cmakedir))
  121. if pn and headers:
  122. for header in headers:
  123. RecipeHandler.recipeheadermap[header] = pn
  124. if pn and cmakefiles:
  125. for fn in cmakefiles:
  126. RecipeHandler.recipecmakefilemap[fn] = pn
  127. @staticmethod
  128. def load_binmap(d):
  129. '''Build up native binary->recipe mapping'''
  130. if RecipeHandler.recipebinmap:
  131. return
  132. sstate_manifests = d.getVar('SSTATE_MANIFESTS')
  133. staging_bindir_native = d.getVar('STAGING_BINDIR_NATIVE')
  134. build_arch = d.getVar('BUILD_ARCH')
  135. fileprefix = 'manifest-%s-' % build_arch
  136. for fn in glob.glob(os.path.join(sstate_manifests, '%s*-native.populate_sysroot' % fileprefix)):
  137. with open(fn, 'r') as f:
  138. pn = os.path.basename(fn).rsplit('.', 1)[0][len(fileprefix):]
  139. for line in f:
  140. if line.startswith(staging_bindir_native):
  141. prog = os.path.basename(line.rstrip())
  142. RecipeHandler.recipebinmap[prog] = pn
  143. @staticmethod
  144. def checkfiles(path, speclist, recursive=False, excludedirs=None):
  145. results = []
  146. if recursive:
  147. for root, dirs, files in os.walk(path, topdown=True):
  148. if excludedirs:
  149. dirs[:] = [d for d in dirs if d not in excludedirs]
  150. for fn in files:
  151. for spec in speclist:
  152. if fnmatch.fnmatch(fn, spec):
  153. results.append(os.path.join(root, fn))
  154. else:
  155. for spec in speclist:
  156. results.extend(glob.glob(os.path.join(path, spec)))
  157. return results
  158. @staticmethod
  159. def handle_depends(libdeps, pcdeps, deps, outlines, values, d):
  160. if pcdeps:
  161. recipemap = read_pkgconfig_provides(d)
  162. if libdeps:
  163. RecipeHandler.load_libmap(d)
  164. ignorelibs = ['socket']
  165. ignoredeps = ['gcc-runtime', 'glibc', 'uclibc', 'musl', 'tar-native', 'binutils-native', 'coreutils-native']
  166. unmappedpc = []
  167. pcdeps = list(set(pcdeps))
  168. for pcdep in pcdeps:
  169. if isinstance(pcdep, str):
  170. recipe = recipemap.get(pcdep, None)
  171. if recipe:
  172. deps.append(recipe)
  173. else:
  174. if not pcdep.startswith('$'):
  175. unmappedpc.append(pcdep)
  176. else:
  177. for item in pcdep:
  178. recipe = recipemap.get(pcdep, None)
  179. if recipe:
  180. deps.append(recipe)
  181. break
  182. else:
  183. unmappedpc.append('(%s)' % ' or '.join(pcdep))
  184. unmappedlibs = []
  185. for libdep in libdeps:
  186. if isinstance(libdep, tuple):
  187. lib, header = libdep
  188. else:
  189. lib = libdep
  190. header = None
  191. if lib in ignorelibs:
  192. logger.debug('Ignoring library dependency %s' % lib)
  193. continue
  194. recipe = RecipeHandler.recipelibmap.get(lib, None)
  195. if recipe:
  196. deps.append(recipe)
  197. elif recipe is None:
  198. if header:
  199. RecipeHandler.load_devel_filemap(d)
  200. recipe = RecipeHandler.recipeheadermap.get(header, None)
  201. if recipe:
  202. deps.append(recipe)
  203. elif recipe is None:
  204. unmappedlibs.append(lib)
  205. else:
  206. unmappedlibs.append(lib)
  207. deps = set(deps).difference(set(ignoredeps))
  208. if unmappedpc:
  209. outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmappedpc))
  210. outlines.append('# (this is based on recipes that have previously been built and packaged)')
  211. if unmappedlibs:
  212. outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmappedlibs))))
  213. outlines.append('# (this is based on recipes that have previously been built and packaged)')
  214. if deps:
  215. values['DEPENDS'] = ' '.join(deps)
  216. @staticmethod
  217. def genfunction(outlines, funcname, content, python=False, forcespace=False):
  218. if python:
  219. prefix = 'python '
  220. else:
  221. prefix = ''
  222. outlines.append('%s%s () {' % (prefix, funcname))
  223. if python or forcespace:
  224. indent = ' '
  225. else:
  226. indent = '\t'
  227. addnoop = not python
  228. for line in content:
  229. outlines.append('%s%s' % (indent, line))
  230. if addnoop:
  231. strippedline = line.lstrip()
  232. if strippedline and not strippedline.startswith('#'):
  233. addnoop = False
  234. if addnoop:
  235. # Without this there'll be a syntax error
  236. outlines.append('%s:' % indent)
  237. outlines.append('}')
  238. outlines.append('')
  239. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  240. return False
  241. def validate_pv(pv):
  242. if not pv or '_version' in pv.lower() or pv[0] not in '0123456789':
  243. return False
  244. return True
  245. def determine_from_filename(srcfile):
  246. """Determine name and version from a filename"""
  247. if is_package(srcfile):
  248. # Force getting the value from the package metadata
  249. return None, None
  250. if '.tar.' in srcfile:
  251. namepart = srcfile.split('.tar.')[0]
  252. else:
  253. namepart = os.path.splitext(srcfile)[0]
  254. namepart = namepart.lower().replace('_', '-')
  255. if namepart.endswith('.src'):
  256. namepart = namepart[:-4]
  257. if namepart.endswith('.orig'):
  258. namepart = namepart[:-5]
  259. splitval = namepart.split('-')
  260. logger.debug('determine_from_filename: split name %s into: %s' % (srcfile, splitval))
  261. ver_re = re.compile('^v?[0-9]')
  262. pv = None
  263. pn = None
  264. if len(splitval) == 1:
  265. # Try to split the version out if there is no separator (or a .)
  266. res = re.match('^([^0-9]+)([0-9.]+.*)$', namepart)
  267. if res:
  268. if len(res.group(1)) > 1 and len(res.group(2)) > 1:
  269. pn = res.group(1).rstrip('.')
  270. pv = res.group(2)
  271. else:
  272. pn = namepart
  273. else:
  274. if splitval[-1] in ['source', 'src']:
  275. splitval.pop()
  276. 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]):
  277. pv = '-'.join(splitval[-2:])
  278. if pv.endswith('-release'):
  279. pv = pv[:-8]
  280. splitval = splitval[:-2]
  281. elif ver_re.match(splitval[-1]):
  282. pv = splitval.pop()
  283. pn = '-'.join(splitval)
  284. if pv and pv.startswith('v'):
  285. pv = pv[1:]
  286. logger.debug('determine_from_filename: name = "%s" version = "%s"' % (pn, pv))
  287. return (pn, pv)
  288. def determine_from_url(srcuri):
  289. """Determine name and version from a URL"""
  290. pn = None
  291. pv = None
  292. parseres = urlparse(srcuri.lower().split(';', 1)[0])
  293. if parseres.path:
  294. if 'github.com' in parseres.netloc:
  295. res = re.search(r'.*/(.*?)/archive/(.*)-final\.(tar|zip)', parseres.path)
  296. if res:
  297. pn = res.group(1).strip().replace('_', '-')
  298. pv = res.group(2).strip().replace('_', '.')
  299. else:
  300. res = re.search(r'.*/(.*?)/archive/v?(.*)\.(tar|zip)', parseres.path)
  301. if res:
  302. pn = res.group(1).strip().replace('_', '-')
  303. pv = res.group(2).strip().replace('_', '.')
  304. elif 'bitbucket.org' in parseres.netloc:
  305. res = re.search(r'.*/(.*?)/get/[a-zA-Z_-]*([0-9][0-9a-zA-Z_.]*)\.(tar|zip)', parseres.path)
  306. if res:
  307. pn = res.group(1).strip().replace('_', '-')
  308. pv = res.group(2).strip().replace('_', '.')
  309. if not pn and not pv:
  310. if parseres.scheme not in ['git', 'gitsm', 'svn', 'hg']:
  311. srcfile = os.path.basename(parseres.path.rstrip('/'))
  312. pn, pv = determine_from_filename(srcfile)
  313. elif parseres.scheme in ['git', 'gitsm']:
  314. pn = os.path.basename(parseres.path.rstrip('/')).lower().replace('_', '-')
  315. if pn.endswith('.git'):
  316. pn = pn[:-4]
  317. logger.debug('Determined from source URL: name = "%s", version = "%s"' % (pn, pv))
  318. return (pn, pv)
  319. def supports_srcrev(uri):
  320. localdata = bb.data.createCopy(tinfoil.config_data)
  321. # This is a bit sad, but if you don't have this set there can be some
  322. # odd interactions with the urldata cache which lead to errors
  323. localdata.setVar('SRCREV', '${AUTOREV}')
  324. try:
  325. fetcher = bb.fetch2.Fetch([uri], localdata)
  326. urldata = fetcher.ud
  327. for u in urldata:
  328. if urldata[u].method.supports_srcrev():
  329. return True
  330. except bb.fetch2.FetchError as e:
  331. logger.debug('FetchError in supports_srcrev: %s' % str(e))
  332. # Fall back to basic check
  333. if uri.startswith(('git://', 'gitsm://')):
  334. return True
  335. return False
  336. def reformat_git_uri(uri):
  337. '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
  338. checkuri = uri.split(';', 1)[0]
  339. if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://github.com/[^/]+/[^/]+/?$', checkuri):
  340. # Appends scheme if the scheme is missing
  341. if not '://' in uri:
  342. uri = 'git://' + uri
  343. scheme, host, path, user, pswd, parms = bb.fetch2.decodeurl(uri)
  344. # Detection mechanism, this is required due to certain URL are formatter with ":" rather than "/"
  345. # which causes decodeurl to fail getting the right host and path
  346. if len(host.split(':')) > 1:
  347. splitslash = host.split(':')
  348. # Port number should not be split from host
  349. if not re.match('^[0-9]+$', splitslash[1]):
  350. host = splitslash[0]
  351. path = '/' + splitslash[1] + path
  352. #Algorithm:
  353. # if user is defined, append protocol=ssh or if a protocol is defined, then honor the user-defined protocol
  354. # if no user & password is defined, check for scheme type and append the protocol with the scheme type
  355. # finally if protocols or if the url is well-formed, do nothing and rejoin everything back to normal
  356. # Need to repackage the arguments for encodeurl, the format is: (scheme, host, path, user, password, OrderedDict([('key', 'value')]))
  357. if user:
  358. if not 'protocol' in parms:
  359. parms.update({('protocol', 'ssh')})
  360. elif (scheme == "http" or scheme == 'https' or scheme == 'ssh') and not ('protocol' in parms):
  361. parms.update({('protocol', scheme)})
  362. # Always append 'git://'
  363. fUrl = bb.fetch2.encodeurl(('git', host, path, user, pswd, parms))
  364. return fUrl
  365. else:
  366. return uri
  367. def is_package(url):
  368. '''Check if a URL points to a package'''
  369. checkurl = url.split(';', 1)[0]
  370. if checkurl.endswith(('.deb', '.ipk', '.rpm', '.srpm')):
  371. return True
  372. return False
  373. def create_recipe(args):
  374. import bb.process
  375. import tempfile
  376. import shutil
  377. import oe.recipeutils
  378. pkgarch = ""
  379. if args.machine:
  380. pkgarch = "${MACHINE_ARCH}"
  381. extravalues = {}
  382. checksums = {}
  383. tempsrc = ''
  384. source = args.source
  385. srcsubdir = ''
  386. srcrev = '${AUTOREV}'
  387. srcbranch = ''
  388. scheme = ''
  389. storeTagName = ''
  390. pv_srcpv = False
  391. if os.path.isfile(source):
  392. source = 'file://%s' % os.path.abspath(source)
  393. if scriptutils.is_src_url(source):
  394. # Warn about github archive URLs
  395. if re.match('https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source):
  396. logger.warning('github archive files are not guaranteed to be stable and may be re-generated over time. If the latter occurs, the checksums will likely change and the recipe will fail at do_fetch. It is recommended that you point to an actual commit or tag in the repository instead (using the repository URL in conjunction with the -S/--srcrev option).')
  397. # Fetch a URL
  398. fetchuri = reformat_git_uri(urldefrag(source)[0])
  399. if args.binary:
  400. # Assume the archive contains the directory structure verbatim
  401. # so we need to extract to a subdirectory
  402. fetchuri += ';subdir=${BP}'
  403. srcuri = fetchuri
  404. rev_re = re.compile(';rev=([^;]+)')
  405. res = rev_re.search(srcuri)
  406. if res:
  407. if args.srcrev:
  408. logger.error('rev= parameter and -S/--srcrev option cannot both be specified - use one or the other')
  409. sys.exit(1)
  410. if args.autorev:
  411. logger.error('rev= parameter and -a/--autorev option cannot both be specified - use one or the other')
  412. sys.exit(1)
  413. srcrev = res.group(1)
  414. srcuri = rev_re.sub('', srcuri)
  415. elif args.srcrev:
  416. srcrev = args.srcrev
  417. # Check whether users provides any branch info in fetchuri.
  418. # If true, we will skip all branch checking process to honor all user's input.
  419. scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(fetchuri)
  420. srcbranch = params.get('branch')
  421. if args.srcbranch:
  422. if srcbranch:
  423. logger.error('branch= parameter and -B/--srcbranch option cannot both be specified - use one or the other')
  424. sys.exit(1)
  425. srcbranch = args.srcbranch
  426. nobranch = params.get('nobranch')
  427. if nobranch and srcbranch:
  428. logger.error('nobranch= cannot be used if you specify a branch')
  429. sys.exit(1)
  430. tag = params.get('tag')
  431. if not srcbranch and not nobranch and srcrev != '${AUTOREV}':
  432. # Append nobranch=1 in the following conditions:
  433. # 1. User did not set 'branch=' in srcuri, and
  434. # 2. User did not set 'nobranch=1' in srcuri, and
  435. # 3. Source revision is not '${AUTOREV}'
  436. params['nobranch'] = '1'
  437. if tag:
  438. # Keep a copy of tag and append nobranch=1 then remove tag from URL.
  439. # Bitbake fetcher unable to fetch when {AUTOREV} and tag is set at the same time.
  440. storeTagName = params['tag']
  441. params['nobranch'] = '1'
  442. del params['tag']
  443. if scheme == 'npm':
  444. params['noverify'] = '1'
  445. fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
  446. tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
  447. bb.utils.mkdirhier(tmpparent)
  448. tempsrc = tempfile.mkdtemp(prefix='recipetool-', dir=tmpparent)
  449. srctree = os.path.join(tempsrc, 'source')
  450. try:
  451. checksums, ftmpdir = scriptutils.fetch_url(tinfoil, fetchuri, srcrev, srctree, logger, preserve_tmp=args.keep_temp)
  452. except scriptutils.FetchUrlFailure as e:
  453. logger.error(str(e))
  454. sys.exit(1)
  455. if ftmpdir and args.keep_temp:
  456. logger.info('Fetch temp directory is %s' % ftmpdir)
  457. dirlist = os.listdir(srctree)
  458. filterout = ['git.indirectionsymlink']
  459. dirlist = [x for x in dirlist if x not in filterout]
  460. logger.debug('Directory listing (excluding filtered out):\n %s' % '\n '.join(dirlist))
  461. if len(dirlist) == 1:
  462. singleitem = os.path.join(srctree, dirlist[0])
  463. if os.path.isdir(singleitem):
  464. # We unpacked a single directory, so we should use that
  465. srcsubdir = dirlist[0]
  466. srctree = os.path.join(srctree, srcsubdir)
  467. else:
  468. check_single_file(dirlist[0], fetchuri)
  469. elif len(dirlist) == 0:
  470. if '/' in fetchuri:
  471. fn = os.path.join(tinfoil.config_data.getVar('DL_DIR'), fetchuri.split('/')[-1])
  472. if os.path.isfile(fn):
  473. check_single_file(fn, fetchuri)
  474. # If we've got to here then there's no source so we might as well give up
  475. logger.error('URL %s resulted in an empty source tree' % fetchuri)
  476. sys.exit(1)
  477. # We need this checking mechanism to improve the recipe created by recipetool and devtool
  478. # is able to parse and build by bitbake.
  479. # If there is no input for branch name, then check for branch name with SRCREV provided.
  480. if not srcbranch and not nobranch and srcrev and (srcrev != '${AUTOREV}') and scheme in ['git', 'gitsm']:
  481. try:
  482. cmd = 'git branch -r --contains'
  483. check_branch, check_branch_err = bb.process.run('%s %s' % (cmd, srcrev), cwd=srctree)
  484. except bb.process.ExecutionError as err:
  485. logger.error(str(err))
  486. sys.exit(1)
  487. get_branch = [x.strip() for x in check_branch.splitlines()]
  488. # Remove HEAD reference point and drop remote prefix
  489. get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
  490. if 'master' in get_branch:
  491. # If it is master, we do not need to append 'branch=master' as this is default.
  492. # Even with the case where get_branch has multiple objects, if 'master' is one
  493. # of them, we should default take from 'master'
  494. srcbranch = ''
  495. elif len(get_branch) == 1:
  496. # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
  497. srcbranch = get_branch[0]
  498. else:
  499. # If get_branch contains more than one objects, then display error and exit.
  500. mbrch = '\n ' + '\n '.join(get_branch)
  501. logger.error('Revision %s was found on multiple branches: %s\nPlease provide the correct branch with -B/--srcbranch' % (srcrev, mbrch))
  502. sys.exit(1)
  503. # Since we might have a value in srcbranch, we need to
  504. # recontruct the srcuri to include 'branch' in params.
  505. scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
  506. if srcbranch:
  507. params['branch'] = srcbranch
  508. if storeTagName and scheme in ['git', 'gitsm']:
  509. # Check srcrev using tag and check validity of the tag
  510. cmd = ('git rev-parse --verify %s' % (storeTagName))
  511. try:
  512. check_tag, check_tag_err = bb.process.run('%s' % cmd, cwd=srctree)
  513. srcrev = check_tag.split()[0]
  514. except bb.process.ExecutionError as err:
  515. logger.error(str(err))
  516. logger.error("Possibly wrong tag name is provided")
  517. sys.exit(1)
  518. # Drop tag from srcuri as it will have conflicts with SRCREV during recipe parse.
  519. del params['tag']
  520. srcuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
  521. if os.path.exists(os.path.join(srctree, '.gitmodules')) and srcuri.startswith('git://'):
  522. srcuri = 'gitsm://' + srcuri[6:]
  523. logger.info('Fetching submodules...')
  524. bb.process.run('git submodule update --init --recursive', cwd=srctree)
  525. if is_package(fetchuri):
  526. localdata = bb.data.createCopy(tinfoil.config_data)
  527. pkgfile = bb.fetch2.localpath(fetchuri, localdata)
  528. if pkgfile:
  529. tmpfdir = tempfile.mkdtemp(prefix='recipetool-')
  530. try:
  531. if pkgfile.endswith(('.deb', '.ipk')):
  532. stdout, _ = bb.process.run('ar x %s' % pkgfile, cwd=tmpfdir)
  533. stdout, _ = bb.process.run('tar xf control.tar.gz', cwd=tmpfdir)
  534. values = convert_debian(tmpfdir)
  535. extravalues.update(values)
  536. elif pkgfile.endswith(('.rpm', '.srpm')):
  537. stdout, _ = bb.process.run('rpm -qp --xml %s > pkginfo.xml' % pkgfile, cwd=tmpfdir)
  538. values = convert_rpm_xml(os.path.join(tmpfdir, 'pkginfo.xml'))
  539. extravalues.update(values)
  540. finally:
  541. shutil.rmtree(tmpfdir)
  542. else:
  543. # Assume we're pointing to an existing source tree
  544. if args.extract_to:
  545. logger.error('--extract-to cannot be specified if source is a directory')
  546. sys.exit(1)
  547. if not os.path.isdir(source):
  548. logger.error('Invalid source directory %s' % source)
  549. sys.exit(1)
  550. srctree = source
  551. srcuri = ''
  552. if os.path.exists(os.path.join(srctree, '.git')):
  553. # Try to get upstream repo location from origin remote
  554. try:
  555. stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
  556. except bb.process.ExecutionError as e:
  557. stdout = None
  558. if stdout:
  559. for line in stdout.splitlines():
  560. splitline = line.split()
  561. if len(splitline) > 1:
  562. if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
  563. srcuri = reformat_git_uri(splitline[1])
  564. srcsubdir = 'git'
  565. break
  566. if args.src_subdir:
  567. srcsubdir = os.path.join(srcsubdir, args.src_subdir)
  568. srctree_use = os.path.abspath(os.path.join(srctree, args.src_subdir))
  569. else:
  570. srctree_use = os.path.abspath(srctree)
  571. if args.outfile and os.path.isdir(args.outfile):
  572. outfile = None
  573. outdir = args.outfile
  574. else:
  575. outfile = args.outfile
  576. outdir = None
  577. if outfile and outfile != '-':
  578. if os.path.exists(outfile):
  579. logger.error('Output file %s already exists' % outfile)
  580. sys.exit(1)
  581. lines_before = []
  582. lines_after = []
  583. lines_before.append('# Recipe created by %s' % os.path.basename(sys.argv[0]))
  584. lines_before.append('# This is the basis of a recipe and may need further editing in order to be fully functional.')
  585. lines_before.append('# (Feel free to remove these comments when editing.)')
  586. # We need a blank line here so that patch_recipe_lines can rewind before the LICENSE comments
  587. lines_before.append('')
  588. # We'll come back and replace this later in handle_license_vars()
  589. lines_before.append('##LICENSE_PLACEHOLDER##')
  590. handled = []
  591. classes = []
  592. # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
  593. pn = None
  594. pv = None
  595. if outfile:
  596. recipefn = os.path.splitext(os.path.basename(outfile))[0]
  597. fnsplit = recipefn.split('_')
  598. if len(fnsplit) > 1:
  599. pn = fnsplit[0]
  600. pv = fnsplit[1]
  601. else:
  602. pn = recipefn
  603. if args.version:
  604. pv = args.version
  605. if args.name:
  606. pn = args.name
  607. if args.name.endswith('-native'):
  608. if args.also_native:
  609. 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')
  610. sys.exit(1)
  611. classes.append('native')
  612. elif args.name.startswith('nativesdk-'):
  613. if args.also_native:
  614. logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
  615. sys.exit(1)
  616. classes.append('nativesdk')
  617. if pv and pv not in 'git svn hg'.split():
  618. realpv = pv
  619. else:
  620. realpv = None
  621. if not srcuri:
  622. lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
  623. lines_before.append('SRC_URI = "%s"' % srcuri)
  624. for key, value in sorted(checksums.items()):
  625. lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
  626. if srcuri and supports_srcrev(srcuri):
  627. lines_before.append('')
  628. lines_before.append('# Modify these as desired')
  629. # Note: we have code to replace realpv further down if it gets set to some other value
  630. scheme, _, _, _, _, _ = bb.fetch2.decodeurl(srcuri)
  631. if scheme in ['git', 'gitsm']:
  632. srcpvprefix = 'git'
  633. elif scheme == 'svn':
  634. srcpvprefix = 'svnr'
  635. else:
  636. srcpvprefix = scheme
  637. lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix))
  638. pv_srcpv = True
  639. if not args.autorev and srcrev == '${AUTOREV}':
  640. if os.path.exists(os.path.join(srctree, '.git')):
  641. (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
  642. srcrev = stdout.rstrip()
  643. lines_before.append('SRCREV = "%s"' % srcrev)
  644. if args.provides:
  645. lines_before.append('PROVIDES = "%s"' % args.provides)
  646. lines_before.append('')
  647. if srcsubdir and not args.binary:
  648. # (for binary packages we explicitly specify subdir= when fetching to
  649. # match the default value of S, so we don't need to set it in that case)
  650. lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
  651. lines_before.append('')
  652. if pkgarch:
  653. lines_after.append('PACKAGE_ARCH = "%s"' % pkgarch)
  654. lines_after.append('')
  655. if args.binary:
  656. lines_after.append('INSANE_SKIP_${PN} += "already-stripped"')
  657. lines_after.append('')
  658. if args.fetch_dev:
  659. extravalues['fetchdev'] = True
  660. else:
  661. extravalues['fetchdev'] = None
  662. # Find all plugins that want to register handlers
  663. logger.debug('Loading recipe handlers')
  664. raw_handlers = []
  665. for plugin in plugins:
  666. if hasattr(plugin, 'register_recipe_handlers'):
  667. plugin.register_recipe_handlers(raw_handlers)
  668. # Sort handlers by priority
  669. handlers = []
  670. for i, handler in enumerate(raw_handlers):
  671. if isinstance(handler, tuple):
  672. handlers.append((handler[0], handler[1], i))
  673. else:
  674. handlers.append((handler, 0, i))
  675. handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
  676. for handler, priority, _ in handlers:
  677. logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
  678. setattr(handler, '_devtool', args.devtool)
  679. handlers = [item[0] for item in handlers]
  680. # Apply the handlers
  681. if args.binary:
  682. classes.append('bin_package')
  683. handled.append('buildsystem')
  684. for handler in handlers:
  685. handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
  686. extrafiles = extravalues.pop('extrafiles', {})
  687. extra_pn = extravalues.pop('PN', None)
  688. extra_pv = extravalues.pop('PV', None)
  689. if extra_pv and not realpv:
  690. realpv = extra_pv
  691. if not validate_pv(realpv):
  692. realpv = None
  693. else:
  694. realpv = realpv.lower().split()[0]
  695. if '_' in realpv:
  696. realpv = realpv.replace('_', '-')
  697. if extra_pn and not pn:
  698. pn = extra_pn
  699. if pn.startswith('GNU '):
  700. pn = pn[4:]
  701. if ' ' in pn:
  702. # Probably a descriptive identifier rather than a proper name
  703. pn = None
  704. else:
  705. pn = pn.lower()
  706. if '_' in pn:
  707. pn = pn.replace('_', '-')
  708. if srcuri and not realpv or not pn:
  709. name_pn, name_pv = determine_from_url(srcuri)
  710. if name_pn and not pn:
  711. pn = name_pn
  712. if name_pv and not realpv:
  713. realpv = name_pv
  714. licvalues = handle_license_vars(srctree_use, lines_before, handled, extravalues, tinfoil.config_data)
  715. if not outfile:
  716. if not pn:
  717. 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)
  718. # devtool looks for this specific exit code, so don't change it
  719. sys.exit(15)
  720. else:
  721. if srcuri and srcuri.startswith(('gitsm://', 'git://', 'hg://', 'svn://')):
  722. suffix = srcuri.split(':', 1)[0]
  723. if suffix == 'gitsm':
  724. suffix = 'git'
  725. outfile = '%s_%s.bb' % (pn, suffix)
  726. elif realpv:
  727. outfile = '%s_%s.bb' % (pn, realpv)
  728. else:
  729. outfile = '%s.bb' % pn
  730. if outdir:
  731. outfile = os.path.join(outdir, outfile)
  732. # We need to check this again
  733. if os.path.exists(outfile):
  734. logger.error('Output file %s already exists' % outfile)
  735. sys.exit(1)
  736. # Move any extra files the plugins created to a directory next to the recipe
  737. if extrafiles:
  738. if outfile == '-':
  739. extraoutdir = pn
  740. else:
  741. extraoutdir = os.path.join(os.path.dirname(outfile), pn)
  742. bb.utils.mkdirhier(extraoutdir)
  743. for destfn, extrafile in extrafiles.items():
  744. shutil.move(extrafile, os.path.join(extraoutdir, destfn))
  745. lines = lines_before
  746. lines_before = []
  747. skipblank = True
  748. for line in lines:
  749. if skipblank:
  750. skipblank = False
  751. if not line:
  752. continue
  753. if line.startswith('S = '):
  754. if realpv and pv not in 'git svn hg'.split():
  755. line = line.replace(realpv, '${PV}')
  756. if pn:
  757. line = line.replace(pn, '${BPN}')
  758. if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
  759. skipblank = True
  760. continue
  761. elif line.startswith('SRC_URI = '):
  762. if realpv and not pv_srcpv:
  763. line = line.replace(realpv, '${PV}')
  764. elif line.startswith('PV = '):
  765. if realpv:
  766. # Replace the first part of the PV value
  767. line = re.sub('"[^+]*\+', '"%s+' % realpv, line)
  768. lines_before.append(line)
  769. if args.also_native:
  770. lines = lines_after
  771. lines_after = []
  772. bbclassextend = None
  773. for line in lines:
  774. if line.startswith('BBCLASSEXTEND ='):
  775. splitval = line.split('"')
  776. if len(splitval) > 1:
  777. bbclassextend = splitval[1].split()
  778. if not 'native' in bbclassextend:
  779. bbclassextend.insert(0, 'native')
  780. line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
  781. lines_after.append(line)
  782. if not bbclassextend:
  783. lines_after.append('BBCLASSEXTEND = "native"')
  784. postinst = ("postinst", extravalues.pop('postinst', None))
  785. postrm = ("postrm", extravalues.pop('postrm', None))
  786. preinst = ("preinst", extravalues.pop('preinst', None))
  787. prerm = ("prerm", extravalues.pop('prerm', None))
  788. funcs = [postinst, postrm, preinst, prerm]
  789. for func in funcs:
  790. if func[1]:
  791. RecipeHandler.genfunction(lines_after, 'pkg_%s_${PN}' % func[0], func[1])
  792. outlines = []
  793. outlines.extend(lines_before)
  794. if classes:
  795. if outlines[-1] and not outlines[-1].startswith('#'):
  796. outlines.append('')
  797. outlines.append('inherit %s' % ' '.join(classes))
  798. outlines.append('')
  799. outlines.extend(lines_after)
  800. if extravalues:
  801. _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False)
  802. if args.extract_to:
  803. scriptutils.git_convert_standalone_clone(srctree)
  804. if os.path.isdir(args.extract_to):
  805. # If the directory exists we'll move the temp dir into it instead of
  806. # its contents - of course, we could try to always move its contents
  807. # but that is a pain if there are symlinks; the simplest solution is
  808. # to just remove it first
  809. os.rmdir(args.extract_to)
  810. shutil.move(srctree, args.extract_to)
  811. if tempsrc == srctree:
  812. tempsrc = None
  813. log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
  814. if outfile == '-':
  815. sys.stdout.write('\n'.join(outlines) + '\n')
  816. else:
  817. with open(outfile, 'w') as f:
  818. lastline = None
  819. for line in outlines:
  820. if not lastline and not line:
  821. # Skip extra blank lines
  822. continue
  823. f.write('%s\n' % line)
  824. lastline = line
  825. log_info_cond('Recipe %s has been created; further editing may be required to make it fully functional' % outfile, args.devtool)
  826. if tempsrc:
  827. if args.keep_temp:
  828. logger.info('Preserving temporary directory %s' % tempsrc)
  829. else:
  830. shutil.rmtree(tempsrc)
  831. return 0
  832. def check_single_file(fn, fetchuri):
  833. """Determine if a single downloaded file is something we can't handle"""
  834. with open(fn, 'r', errors='surrogateescape') as f:
  835. if '<html' in f.read(100).lower():
  836. logger.error('Fetching "%s" returned a single HTML page - check the URL is correct and functional' % fetchuri)
  837. sys.exit(1)
  838. def split_value(value):
  839. if isinstance(value, str):
  840. return value.split()
  841. else:
  842. return value
  843. def handle_license_vars(srctree, lines_before, handled, extravalues, d):
  844. lichandled = [x for x in handled if x[0] == 'license']
  845. if lichandled:
  846. # Someone else has already handled the license vars, just return their value
  847. return lichandled[0][1]
  848. licvalues = guess_license(srctree, d)
  849. licenses = []
  850. lic_files_chksum = []
  851. lic_unknown = []
  852. lines = []
  853. if licvalues:
  854. for licvalue in licvalues:
  855. if not licvalue[0] in licenses:
  856. licenses.append(licvalue[0])
  857. lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
  858. if licvalue[0] == 'Unknown':
  859. lic_unknown.append(licvalue[1])
  860. if lic_unknown:
  861. lines.append('#')
  862. lines.append('# The following license files were not able to be identified and are')
  863. lines.append('# represented as "Unknown" below, you will need to check them yourself:')
  864. for licfile in lic_unknown:
  865. lines.append('# %s' % licfile)
  866. extra_license = split_value(extravalues.pop('LICENSE', []))
  867. if '&' in extra_license:
  868. extra_license.remove('&')
  869. if extra_license:
  870. if licenses == ['Unknown']:
  871. licenses = extra_license
  872. else:
  873. for item in extra_license:
  874. if item not in licenses:
  875. licenses.append(item)
  876. extra_lic_files_chksum = split_value(extravalues.pop('LIC_FILES_CHKSUM', []))
  877. for item in extra_lic_files_chksum:
  878. if item not in lic_files_chksum:
  879. lic_files_chksum.append(item)
  880. if lic_files_chksum:
  881. # We are going to set the vars, so prepend the standard disclaimer
  882. lines.insert(0, '# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is')
  883. lines.insert(1, '# your responsibility to verify that the values are complete and correct.')
  884. else:
  885. # Without LIC_FILES_CHKSUM we set LICENSE = "CLOSED" to allow the
  886. # user to get started easily
  887. lines.append('# Unable to find any files that looked like license statements. Check the accompanying')
  888. lines.append('# documentation and source headers and set LICENSE and LIC_FILES_CHKSUM accordingly.')
  889. lines.append('#')
  890. lines.append('# NOTE: LICENSE is being set to "CLOSED" to allow you to at least start building - if')
  891. lines.append('# this is not accurate with respect to the licensing of the software being built (it')
  892. lines.append('# will not be in most cases) you must specify the correct value before using this')
  893. lines.append('# recipe for anything other than initial testing/development!')
  894. licenses = ['CLOSED']
  895. if extra_license and sorted(licenses) != sorted(extra_license):
  896. lines.append('# NOTE: Original package / source metadata indicates license is: %s' % ' & '.join(extra_license))
  897. if len(licenses) > 1:
  898. lines.append('#')
  899. lines.append('# NOTE: multiple licenses have been detected; they have been separated with &')
  900. lines.append('# in the LICENSE value for now since it is a reasonable assumption that all')
  901. lines.append('# of the licenses apply. If instead there is a choice between the multiple')
  902. lines.append('# licenses then you should change the value to separate the licenses with |')
  903. lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
  904. lines.append('# to determine which situation is applicable.')
  905. lines.append('LICENSE = "%s"' % ' & '.join(licenses))
  906. lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
  907. lines.append('')
  908. # Replace the placeholder so we get the values in the right place in the recipe file
  909. try:
  910. pos = lines_before.index('##LICENSE_PLACEHOLDER##')
  911. except ValueError:
  912. pos = -1
  913. if pos == -1:
  914. lines_before.extend(lines)
  915. else:
  916. lines_before[pos:pos+1] = lines
  917. handled.append(('license', licvalues))
  918. return licvalues
  919. def get_license_md5sums(d, static_only=False):
  920. import bb.utils
  921. md5sums = {}
  922. if not static_only:
  923. # Gather md5sums of license files in common license dir
  924. commonlicdir = d.getVar('COMMON_LICENSE_DIR')
  925. for fn in os.listdir(commonlicdir):
  926. md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
  927. md5sums[md5value] = fn
  928. # The following were extracted from common values in various recipes
  929. # (double checking the license against the license file itself, not just
  930. # the LICENSE value in the recipe)
  931. md5sums['94d55d512a9ba36caa9b7df079bae19f'] = 'GPLv2'
  932. md5sums['b234ee4d69f5fce4486a80fdaf4a4263'] = 'GPLv2'
  933. md5sums['59530bdf33659b29e73d4adb9f9f6552'] = 'GPLv2'
  934. md5sums['0636e73ff0215e8d672dc4c32c317bb3'] = 'GPLv2'
  935. md5sums['eb723b61539feef013de476e68b5c50a'] = 'GPLv2'
  936. md5sums['751419260aa954499f7abaabaa882bbe'] = 'GPLv2'
  937. md5sums['393a5ca445f6965873eca0259a17f833'] = 'GPLv2'
  938. md5sums['12f884d2ae1ff87c09e5b7ccc2c4ca7e'] = 'GPLv2'
  939. md5sums['8ca43cbc842c2336e835926c2166c28b'] = 'GPLv2'
  940. md5sums['ebb5c50ab7cab4baeffba14977030c07'] = 'GPLv2'
  941. md5sums['c93c0550bd3173f4504b2cbd8991e50b'] = 'GPLv2'
  942. md5sums['9ac2e7cff1ddaf48b6eab6028f23ef88'] = 'GPLv2'
  943. md5sums['4325afd396febcb659c36b49533135d4'] = 'GPLv2'
  944. md5sums['18810669f13b87348459e611d31ab760'] = 'GPLv2'
  945. md5sums['d7810fab7487fb0aad327b76f1be7cd7'] = 'GPLv2' # the Linux kernel's COPYING file
  946. md5sums['bbb461211a33b134d42ed5ee802b37ff'] = 'LGPLv2.1'
  947. md5sums['7fbc338309ac38fefcd64b04bb903e34'] = 'LGPLv2.1'
  948. md5sums['4fbd65380cdd255951079008b364516c'] = 'LGPLv2.1'
  949. md5sums['2d5025d4aa3495befef8f17206a5b0a1'] = 'LGPLv2.1'
  950. md5sums['fbc093901857fcd118f065f900982c24'] = 'LGPLv2.1'
  951. md5sums['a6f89e2100d9b6cdffcea4f398e37343'] = 'LGPLv2.1'
  952. md5sums['d8045f3b8f929c1cb29a1e3fd737b499'] = 'LGPLv2.1'
  953. md5sums['fad9b3332be894bab9bc501572864b29'] = 'LGPLv2.1'
  954. md5sums['3bf50002aefd002f49e7bb854063f7e7'] = 'LGPLv2'
  955. md5sums['9f604d8a4f8e74f4f5140845a21b6674'] = 'LGPLv2'
  956. md5sums['5f30f0716dfdd0d91eb439ebec522ec2'] = 'LGPLv2'
  957. md5sums['55ca817ccb7d5b5b66355690e9abc605'] = 'LGPLv2'
  958. md5sums['252890d9eee26aab7b432e8b8a616475'] = 'LGPLv2'
  959. md5sums['3214f080875748938ba060314b4f727d'] = 'LGPLv2'
  960. md5sums['db979804f025cf55aabec7129cb671ed'] = 'LGPLv2'
  961. md5sums['d32239bcb673463ab874e80d47fae504'] = 'GPLv3'
  962. md5sums['f27defe1e96c2e1ecd4e0c9be8967949'] = 'GPLv3'
  963. md5sums['6a6a8e020838b23406c81b19c1d46df6'] = 'LGPLv3'
  964. md5sums['3b83ef96387f14655fc854ddc3c6bd57'] = 'Apache-2.0'
  965. md5sums['385c55653886acac3821999a3ccd17b3'] = 'Artistic-1.0 | GPL-2.0' # some perl modules
  966. md5sums['54c7042be62e169199200bc6477f04d1'] = 'BSD-3-Clause'
  967. return md5sums
  968. def crunch_license(licfile):
  969. '''
  970. Remove non-material text from a license file and then check
  971. its md5sum against a known list. This works well for licenses
  972. which contain a copyright statement, but is also a useful way
  973. to handle people's insistence upon reformatting the license text
  974. slightly (with no material difference to the text of the
  975. license).
  976. '''
  977. import oe.utils
  978. # Note: these are carefully constructed!
  979. license_title_re = re.compile('^\(?(#+ *)?(The )?.{1,10} [Ll]icen[sc]e( \(.{1,10}\))?\)?:?$')
  980. 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:?$')
  981. copyright_re = re.compile('^(#+)? *Copyright .*$')
  982. crunched_md5sums = {}
  983. # The following two were gleaned from the "forever" npm package
  984. crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
  985. crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
  986. # https://github.com/vasi/pixz/blob/master/LICENSE
  987. crunched_md5sums['2f03392b40bbe663597b5bd3cc5ebdb9'] = 'BSD-2-Clause'
  988. # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
  989. crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause'
  990. # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
  991. crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2'
  992. # https://github.com/datto/dattobd/blob/master/COPYING
  993. # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT
  994. crunched_md5sums['1d65c5ad4bf6489f85f4812bf08ae73d'] = 'GPLv2'
  995. # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
  996. # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD
  997. crunched_md5sums['fb530f66a7a89ce920f0e912b5b66d4b'] = 'GPLv2'
  998. # https://github.com/gkos/nrf24/blob/master/COPYING
  999. crunched_md5sums['7b6aaa4daeafdfa6ed5443fd2684581b'] = 'GPLv2'
  1000. # https://github.com/josch09/resetusb/blob/master/COPYING
  1001. crunched_md5sums['8b8ac1d631a4d220342e83bcf1a1fbc3'] = 'GPLv3'
  1002. # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
  1003. crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1'
  1004. # unixODBC-2.3.4 COPYING
  1005. crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1'
  1006. # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
  1007. crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3'
  1008. # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
  1009. crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
  1010. # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/edl-v10
  1011. crunched_md5sums['0a9c78c0a398d1bbce4a166757d60387'] = 'EDL-1.0'
  1012. lictext = []
  1013. with open(licfile, 'r', errors='surrogateescape') as f:
  1014. for line in f:
  1015. # Drop opening statements
  1016. if copyright_re.match(line):
  1017. continue
  1018. elif license_title_re.match(line):
  1019. continue
  1020. elif license_statement_re.match(line):
  1021. continue
  1022. # Squash spaces, and replace smart quotes, double quotes
  1023. # and backticks with single quotes
  1024. line = oe.utils.squashspaces(line.strip())
  1025. line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
  1026. if line:
  1027. lictext.append(line)
  1028. m = hashlib.md5()
  1029. try:
  1030. m.update(' '.join(lictext).encode('utf-8'))
  1031. md5val = m.hexdigest()
  1032. except UnicodeEncodeError:
  1033. md5val = None
  1034. lictext = ''
  1035. license = crunched_md5sums.get(md5val, None)
  1036. return license, md5val, lictext
  1037. def guess_license(srctree, d):
  1038. import bb
  1039. md5sums = get_license_md5sums(d)
  1040. licenses = []
  1041. licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
  1042. licfiles = []
  1043. for root, dirs, files in os.walk(srctree):
  1044. for fn in files:
  1045. for spec in licspecs:
  1046. if fnmatch.fnmatch(fn, spec):
  1047. fullpath = os.path.join(root, fn)
  1048. if not fullpath in licfiles:
  1049. licfiles.append(fullpath)
  1050. for licfile in licfiles:
  1051. md5value = bb.utils.md5_file(licfile)
  1052. license = md5sums.get(md5value, None)
  1053. if not license:
  1054. license, crunched_md5, lictext = crunch_license(licfile)
  1055. if not license:
  1056. license = 'Unknown'
  1057. licenses.append((license, os.path.relpath(licfile, srctree), md5value))
  1058. # FIXME should we grab at least one source file with a license header and add that too?
  1059. return licenses
  1060. def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
  1061. """
  1062. Given a list of (license, path, md5sum) as returned by guess_license(),
  1063. a dict of package name to path mappings, write out a set of
  1064. package-specific LICENSE values.
  1065. """
  1066. pkglicenses = {pn: []}
  1067. for license, licpath, _ in licvalues:
  1068. for pkgname, pkgpath in packages.items():
  1069. if licpath.startswith(pkgpath + '/'):
  1070. if pkgname in pkglicenses:
  1071. pkglicenses[pkgname].append(license)
  1072. else:
  1073. pkglicenses[pkgname] = [license]
  1074. break
  1075. else:
  1076. # Accumulate on the main package
  1077. pkglicenses[pn].append(license)
  1078. outlicenses = {}
  1079. for pkgname in packages:
  1080. license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
  1081. if license == 'Unknown' and pkgname in fallback_licenses:
  1082. license = fallback_licenses[pkgname]
  1083. outlines.append('LICENSE_%s = "%s"' % (pkgname, license))
  1084. outlicenses[pkgname] = license.split()
  1085. return outlicenses
  1086. def read_pkgconfig_provides(d):
  1087. pkgdatadir = d.getVar('PKGDATA_DIR')
  1088. pkgmap = {}
  1089. for fn in glob.glob(os.path.join(pkgdatadir, 'shlibs2', '*.pclist')):
  1090. with open(fn, 'r') as f:
  1091. for line in f:
  1092. pkgmap[os.path.basename(line.rstrip())] = os.path.splitext(os.path.basename(fn))[0]
  1093. recipemap = {}
  1094. for pc, pkg in pkgmap.items():
  1095. pkgdatafile = os.path.join(pkgdatadir, 'runtime', pkg)
  1096. if os.path.exists(pkgdatafile):
  1097. with open(pkgdatafile, 'r') as f:
  1098. for line in f:
  1099. if line.startswith('PN: '):
  1100. recipemap[pc] = line.split(':', 1)[1].strip()
  1101. return recipemap
  1102. def convert_debian(debpath):
  1103. value_map = {'Package': 'PN',
  1104. 'Version': 'PV',
  1105. 'Section': 'SECTION',
  1106. 'License': 'LICENSE',
  1107. 'Homepage': 'HOMEPAGE'}
  1108. # FIXME extend this mapping - perhaps use distro_alias.inc?
  1109. depmap = {'libz-dev': 'zlib'}
  1110. values = {}
  1111. depends = []
  1112. with open(os.path.join(debpath, 'control'), 'r', errors='surrogateescape') as f:
  1113. indesc = False
  1114. for line in f:
  1115. if indesc:
  1116. if line.startswith(' '):
  1117. if line.startswith(' This package contains'):
  1118. indesc = False
  1119. else:
  1120. if 'DESCRIPTION' in values:
  1121. values['DESCRIPTION'] += ' ' + line.strip()
  1122. else:
  1123. values['DESCRIPTION'] = line.strip()
  1124. else:
  1125. indesc = False
  1126. if not indesc:
  1127. splitline = line.split(':', 1)
  1128. if len(splitline) < 2:
  1129. continue
  1130. key = splitline[0]
  1131. value = splitline[1].strip()
  1132. if key == 'Build-Depends':
  1133. for dep in value.split(','):
  1134. dep = dep.split()[0]
  1135. mapped = depmap.get(dep, '')
  1136. if mapped:
  1137. depends.append(mapped)
  1138. elif key == 'Description':
  1139. values['SUMMARY'] = value
  1140. indesc = True
  1141. else:
  1142. varname = value_map.get(key, None)
  1143. if varname:
  1144. values[varname] = value
  1145. postinst = os.path.join(debpath, 'postinst')
  1146. postrm = os.path.join(debpath, 'postrm')
  1147. preinst = os.path.join(debpath, 'preinst')
  1148. prerm = os.path.join(debpath, 'prerm')
  1149. sfiles = [postinst, postrm, preinst, prerm]
  1150. for sfile in sfiles:
  1151. if os.path.isfile(sfile):
  1152. logger.info("Converting %s file to recipe function..." %
  1153. os.path.basename(sfile).upper())
  1154. content = []
  1155. with open(sfile) as f:
  1156. for line in f:
  1157. if "#!/" in line:
  1158. continue
  1159. line = line.rstrip("\n")
  1160. if line.strip():
  1161. content.append(line)
  1162. if content:
  1163. values[os.path.basename(f.name)] = content
  1164. #if depends:
  1165. # values['DEPENDS'] = ' '.join(depends)
  1166. return values
  1167. def convert_rpm_xml(xmlfile):
  1168. '''Converts the output from rpm -qp --xml to a set of variable values'''
  1169. import xml.etree.ElementTree as ElementTree
  1170. rpmtag_map = {'Name': 'PN',
  1171. 'Version': 'PV',
  1172. 'Summary': 'SUMMARY',
  1173. 'Description': 'DESCRIPTION',
  1174. 'License': 'LICENSE',
  1175. 'Url': 'HOMEPAGE'}
  1176. values = {}
  1177. tree = ElementTree.parse(xmlfile)
  1178. root = tree.getroot()
  1179. for child in root:
  1180. if child.tag == 'rpmTag':
  1181. name = child.attrib.get('name', None)
  1182. if name:
  1183. varname = rpmtag_map.get(name, None)
  1184. if varname:
  1185. values[varname] = child[0].text
  1186. return values
  1187. def register_commands(subparsers):
  1188. parser_create = subparsers.add_parser('create',
  1189. help='Create a new recipe',
  1190. description='Creates a new recipe from a source tree')
  1191. parser_create.add_argument('source', help='Path or URL to source')
  1192. parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
  1193. parser_create.add_argument('-p', '--provides', help='Specify an alias for the item provided by the recipe')
  1194. parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
  1195. 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')
  1196. parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
  1197. parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
  1198. 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')
  1199. 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')
  1200. parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
  1201. group = parser_create.add_mutually_exclusive_group()
  1202. 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")
  1203. group.add_argument('-S', '--srcrev', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
  1204. parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)')
  1205. parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
  1206. parser_create.add_argument('--fetch-dev', action="store_true", help='For npm, also fetch devDependencies')
  1207. parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
  1208. parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
  1209. parser_create.set_defaults(func=create_recipe)