create_buildsys.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. # Recipe creation tool - create command build system handlers
  2. #
  3. # Copyright (C) 2014-2016 Intel Corporation
  4. #
  5. # SPDX-License-Identifier: GPL-2.0-only
  6. #
  7. import re
  8. import logging
  9. import glob
  10. from recipetool.create import RecipeHandler, validate_pv
  11. logger = logging.getLogger('recipetool')
  12. tinfoil = None
  13. plugins = None
  14. def plugin_init(pluginlist):
  15. # Take a reference to the list so we can use it later
  16. global plugins
  17. plugins = pluginlist
  18. def tinfoil_init(instance):
  19. global tinfoil
  20. tinfoil = instance
  21. class CmakeRecipeHandler(RecipeHandler):
  22. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  23. if 'buildsystem' in handled:
  24. return False
  25. if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']):
  26. classes.append('cmake')
  27. values = CmakeRecipeHandler.extract_cmake_deps(lines_before, srctree, extravalues)
  28. classes.extend(values.pop('inherit', '').split())
  29. for var, value in values.items():
  30. lines_before.append('%s = "%s"' % (var, value))
  31. lines_after.append('# Specify any options you want to pass to cmake using EXTRA_OECMAKE:')
  32. lines_after.append('EXTRA_OECMAKE = ""')
  33. lines_after.append('')
  34. handled.append('buildsystem')
  35. return True
  36. return False
  37. @staticmethod
  38. def extract_cmake_deps(outlines, srctree, extravalues, cmakelistsfile=None):
  39. # Find all plugins that want to register handlers
  40. logger.debug('Loading cmake handlers')
  41. handlers = []
  42. for plugin in plugins:
  43. if hasattr(plugin, 'register_cmake_handlers'):
  44. plugin.register_cmake_handlers(handlers)
  45. values = {}
  46. inherits = []
  47. if cmakelistsfile:
  48. srcfiles = [cmakelistsfile]
  49. else:
  50. srcfiles = RecipeHandler.checkfiles(srctree, ['CMakeLists.txt'])
  51. # Note that some of these are non-standard, but probably better to
  52. # be able to map them anyway if we see them
  53. cmake_pkgmap = {'alsa': 'alsa-lib',
  54. 'aspell': 'aspell',
  55. 'atk': 'atk',
  56. 'bison': 'bison-native',
  57. 'boost': 'boost',
  58. 'bzip2': 'bzip2',
  59. 'cairo': 'cairo',
  60. 'cups': 'cups',
  61. 'curl': 'curl',
  62. 'curses': 'ncurses',
  63. 'cvs': 'cvs',
  64. 'drm': 'libdrm',
  65. 'dbus': 'dbus',
  66. 'dbusglib': 'dbus-glib',
  67. 'egl': 'virtual/egl',
  68. 'expat': 'expat',
  69. 'flex': 'flex-native',
  70. 'fontconfig': 'fontconfig',
  71. 'freetype': 'freetype',
  72. 'gettext': '',
  73. 'git': '',
  74. 'gio': 'glib-2.0',
  75. 'giounix': 'glib-2.0',
  76. 'glew': 'glew',
  77. 'glib': 'glib-2.0',
  78. 'glib2': 'glib-2.0',
  79. 'glu': 'libglu',
  80. 'glut': 'freeglut',
  81. 'gobject': 'glib-2.0',
  82. 'gperf': 'gperf-native',
  83. 'gnutls': 'gnutls',
  84. 'gtk2': 'gtk+',
  85. 'gtk3': 'gtk+3',
  86. 'gtk': 'gtk+3',
  87. 'harfbuzz': 'harfbuzz',
  88. 'icu': 'icu',
  89. 'intl': 'virtual/libintl',
  90. 'jpeg': 'jpeg',
  91. 'libarchive': 'libarchive',
  92. 'libiconv': 'virtual/libiconv',
  93. 'liblzma': 'xz',
  94. 'libxml2': 'libxml2',
  95. 'libxslt': 'libxslt',
  96. 'opengl': 'virtual/libgl',
  97. 'openmp': '',
  98. 'openssl': 'openssl',
  99. 'pango': 'pango',
  100. 'perl': '',
  101. 'perllibs': '',
  102. 'pkgconfig': '',
  103. 'png': 'libpng',
  104. 'pthread': '',
  105. 'pythoninterp': '',
  106. 'pythonlibs': '',
  107. 'ruby': 'ruby-native',
  108. 'sdl': 'libsdl',
  109. 'sdl2': 'libsdl2',
  110. 'subversion': 'subversion-native',
  111. 'swig': 'swig-native',
  112. 'tcl': 'tcl-native',
  113. 'threads': '',
  114. 'tiff': 'tiff',
  115. 'wget': 'wget',
  116. 'x11': 'libx11',
  117. 'xcb': 'libxcb',
  118. 'xext': 'libxext',
  119. 'xfixes': 'libxfixes',
  120. 'zlib': 'zlib',
  121. }
  122. pcdeps = []
  123. libdeps = []
  124. deps = []
  125. unmappedpkgs = []
  126. proj_re = re.compile('project\s*\(([^)]*)\)', re.IGNORECASE)
  127. pkgcm_re = re.compile('pkg_check_modules\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?\s+([^)\s]+)\s*\)', re.IGNORECASE)
  128. pkgsm_re = re.compile('pkg_search_module\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?((\s+[^)\s]+)+)\s*\)', re.IGNORECASE)
  129. findpackage_re = re.compile('find_package\s*\(\s*([a-zA-Z0-9-_]+)\s*.*', re.IGNORECASE)
  130. findlibrary_re = re.compile('find_library\s*\(\s*[a-zA-Z0-9-_]+\s*(NAMES\s+)?([a-zA-Z0-9-_ ]+)\s*.*')
  131. checklib_re = re.compile('check_library_exists\s*\(\s*([^\s)]+)\s*.*', re.IGNORECASE)
  132. include_re = re.compile('include\s*\(\s*([^)\s]*)\s*\)', re.IGNORECASE)
  133. subdir_re = re.compile('add_subdirectory\s*\(\s*([^)\s]*)\s*([^)\s]*)\s*\)', re.IGNORECASE)
  134. dep_re = re.compile('([^ ><=]+)( *[<>=]+ *[^ ><=]+)?')
  135. def find_cmake_package(pkg):
  136. RecipeHandler.load_devel_filemap(tinfoil.config_data)
  137. for fn, pn in RecipeHandler.recipecmakefilemap.items():
  138. splitname = fn.split('/')
  139. if len(splitname) > 1:
  140. if splitname[0].lower().startswith(pkg.lower()):
  141. if splitname[1] == '%s-config.cmake' % pkg.lower() or splitname[1] == '%sConfig.cmake' % pkg or splitname[1] == 'Find%s.cmake' % pkg:
  142. return pn
  143. return None
  144. def interpret_value(value):
  145. return value.strip('"')
  146. def parse_cmake_file(fn, paths=None):
  147. searchpaths = (paths or []) + [os.path.dirname(fn)]
  148. logger.debug('Parsing file %s' % fn)
  149. with open(fn, 'r', errors='surrogateescape') as f:
  150. for line in f:
  151. line = line.strip()
  152. for handler in handlers:
  153. if handler.process_line(srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values):
  154. continue
  155. res = include_re.match(line)
  156. if res:
  157. includefn = bb.utils.which(':'.join(searchpaths), res.group(1))
  158. if includefn:
  159. parse_cmake_file(includefn, searchpaths)
  160. else:
  161. logger.debug('Unable to recurse into include file %s' % res.group(1))
  162. continue
  163. res = subdir_re.match(line)
  164. if res:
  165. subdirfn = os.path.join(os.path.dirname(fn), res.group(1), 'CMakeLists.txt')
  166. if os.path.exists(subdirfn):
  167. parse_cmake_file(subdirfn, searchpaths)
  168. else:
  169. logger.debug('Unable to recurse into subdirectory file %s' % subdirfn)
  170. continue
  171. res = proj_re.match(line)
  172. if res:
  173. extravalues['PN'] = interpret_value(res.group(1).split()[0])
  174. continue
  175. res = pkgcm_re.match(line)
  176. if res:
  177. res = dep_re.findall(res.group(2))
  178. if res:
  179. pcdeps.extend([interpret_value(x[0]) for x in res])
  180. inherits.append('pkgconfig')
  181. continue
  182. res = pkgsm_re.match(line)
  183. if res:
  184. res = dep_re.findall(res.group(2))
  185. if res:
  186. # Note: appending a tuple here!
  187. item = tuple((interpret_value(x[0]) for x in res))
  188. if len(item) == 1:
  189. item = item[0]
  190. pcdeps.append(item)
  191. inherits.append('pkgconfig')
  192. continue
  193. res = findpackage_re.match(line)
  194. if res:
  195. origpkg = res.group(1)
  196. pkg = interpret_value(origpkg)
  197. found = False
  198. for handler in handlers:
  199. if handler.process_findpackage(srctree, fn, pkg, deps, outlines, inherits, values):
  200. logger.debug('Mapped CMake package %s via handler %s' % (pkg, handler.__class__.__name__))
  201. found = True
  202. break
  203. if found:
  204. continue
  205. elif pkg == 'Gettext':
  206. inherits.append('gettext')
  207. elif pkg == 'Perl':
  208. inherits.append('perlnative')
  209. elif pkg == 'PkgConfig':
  210. inherits.append('pkgconfig')
  211. elif pkg == 'PythonInterp':
  212. inherits.append('python3native')
  213. elif pkg == 'PythonLibs':
  214. inherits.append('python3-dir')
  215. else:
  216. # Try to map via looking at installed CMake packages in pkgdata
  217. dep = find_cmake_package(pkg)
  218. if dep:
  219. logger.debug('Mapped CMake package %s to recipe %s via pkgdata' % (pkg, dep))
  220. deps.append(dep)
  221. else:
  222. dep = cmake_pkgmap.get(pkg.lower(), None)
  223. if dep:
  224. logger.debug('Mapped CMake package %s to recipe %s via internal list' % (pkg, dep))
  225. deps.append(dep)
  226. elif dep is None:
  227. unmappedpkgs.append(origpkg)
  228. continue
  229. res = checklib_re.match(line)
  230. if res:
  231. lib = interpret_value(res.group(1))
  232. if not lib.startswith('$'):
  233. libdeps.append(lib)
  234. res = findlibrary_re.match(line)
  235. if res:
  236. libs = res.group(2).split()
  237. for lib in libs:
  238. if lib in ['HINTS', 'PATHS', 'PATH_SUFFIXES', 'DOC', 'NAMES_PER_DIR'] or lib.startswith(('NO_', 'CMAKE_', 'ONLY_CMAKE_')):
  239. break
  240. lib = interpret_value(lib)
  241. if not lib.startswith('$'):
  242. libdeps.append(lib)
  243. if line.lower().startswith('useswig'):
  244. deps.append('swig-native')
  245. continue
  246. parse_cmake_file(srcfiles[0])
  247. if unmappedpkgs:
  248. outlines.append('# NOTE: unable to map the following CMake package dependencies: %s' % ' '.join(list(set(unmappedpkgs))))
  249. RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)
  250. for handler in handlers:
  251. handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)
  252. if inherits:
  253. values['inherit'] = ' '.join(list(set(inherits)))
  254. return values
  255. class CmakeExtensionHandler(object):
  256. '''Base class for CMake extension handlers'''
  257. def process_line(self, srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values):
  258. '''
  259. Handle a line parsed out of an CMake file.
  260. Return True if you've completely handled the passed in line, otherwise return False.
  261. '''
  262. return False
  263. def process_findpackage(self, srctree, fn, pkg, deps, outlines, inherits, values):
  264. '''
  265. Handle a find_package package parsed out of a CMake file.
  266. Return True if you've completely handled the passed in package, otherwise return False.
  267. '''
  268. return False
  269. def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values):
  270. '''
  271. Apply any desired post-processing on the output
  272. '''
  273. return
  274. class SconsRecipeHandler(RecipeHandler):
  275. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  276. if 'buildsystem' in handled:
  277. return False
  278. if RecipeHandler.checkfiles(srctree, ['SConstruct', 'Sconstruct', 'sconstruct']):
  279. classes.append('scons')
  280. lines_after.append('# Specify any options you want to pass to scons using EXTRA_OESCONS:')
  281. lines_after.append('EXTRA_OESCONS = ""')
  282. lines_after.append('')
  283. handled.append('buildsystem')
  284. return True
  285. return False
  286. class QmakeRecipeHandler(RecipeHandler):
  287. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  288. if 'buildsystem' in handled:
  289. return False
  290. if RecipeHandler.checkfiles(srctree, ['*.pro']):
  291. classes.append('qmake2')
  292. handled.append('buildsystem')
  293. return True
  294. return False
  295. class AutotoolsRecipeHandler(RecipeHandler):
  296. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  297. if 'buildsystem' in handled:
  298. return False
  299. autoconf = False
  300. if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']):
  301. autoconf = True
  302. values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, extravalues)
  303. classes.extend(values.pop('inherit', '').split())
  304. for var, value in values.items():
  305. lines_before.append('%s = "%s"' % (var, value))
  306. else:
  307. conffile = RecipeHandler.checkfiles(srctree, ['configure'])
  308. if conffile:
  309. # Check if this is just a pre-generated autoconf configure script
  310. with open(conffile[0], 'r', errors='surrogateescape') as f:
  311. for i in range(1, 10):
  312. if 'Generated by GNU Autoconf' in f.readline():
  313. autoconf = True
  314. break
  315. if autoconf and not ('PV' in extravalues and 'PN' in extravalues):
  316. # Last resort
  317. conffile = RecipeHandler.checkfiles(srctree, ['configure'])
  318. if conffile:
  319. with open(conffile[0], 'r', errors='surrogateescape') as f:
  320. for line in f:
  321. line = line.strip()
  322. if line.startswith('VERSION=') or line.startswith('PACKAGE_VERSION='):
  323. pv = line.split('=')[1].strip('"\'')
  324. if pv and not 'PV' in extravalues and validate_pv(pv):
  325. extravalues['PV'] = pv
  326. elif line.startswith('PACKAGE_NAME=') or line.startswith('PACKAGE='):
  327. pn = line.split('=')[1].strip('"\'')
  328. if pn and not 'PN' in extravalues:
  329. extravalues['PN'] = pn
  330. if autoconf:
  331. lines_before.append('')
  332. lines_before.append('# NOTE: if this software is not capable of being built in a separate build directory')
  333. lines_before.append('# from the source, you should replace autotools with autotools-brokensep in the')
  334. lines_before.append('# inherit line')
  335. classes.append('autotools')
  336. lines_after.append('# Specify any options you want to pass to the configure script using EXTRA_OECONF:')
  337. lines_after.append('EXTRA_OECONF = ""')
  338. lines_after.append('')
  339. handled.append('buildsystem')
  340. return True
  341. return False
  342. @staticmethod
  343. def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None):
  344. import shlex
  345. # Find all plugins that want to register handlers
  346. logger.debug('Loading autotools handlers')
  347. handlers = []
  348. for plugin in plugins:
  349. if hasattr(plugin, 'register_autotools_handlers'):
  350. plugin.register_autotools_handlers(handlers)
  351. values = {}
  352. inherits = []
  353. # Hardcoded map, we also use a dynamic one based on what's in the sysroot
  354. progmap = {'flex': 'flex-native',
  355. 'bison': 'bison-native',
  356. 'm4': 'm4-native',
  357. 'tar': 'tar-native',
  358. 'ar': 'binutils-native',
  359. 'ranlib': 'binutils-native',
  360. 'ld': 'binutils-native',
  361. 'strip': 'binutils-native',
  362. 'libtool': '',
  363. 'autoconf': '',
  364. 'autoheader': '',
  365. 'automake': '',
  366. 'uname': '',
  367. 'rm': '',
  368. 'cp': '',
  369. 'mv': '',
  370. 'find': '',
  371. 'awk': '',
  372. 'sed': '',
  373. }
  374. progclassmap = {'gconftool-2': 'gconf',
  375. 'pkg-config': 'pkgconfig',
  376. 'python': 'python3native',
  377. 'python3': 'python3native',
  378. 'perl': 'perlnative',
  379. 'makeinfo': 'texinfo',
  380. }
  381. pkg_re = re.compile('PKG_CHECK_MODULES\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*')
  382. pkgce_re = re.compile('PKG_CHECK_EXISTS\(\s*\[?([^,\]]*)\]?[),].*')
  383. lib_re = re.compile('AC_CHECK_LIB\(\s*\[?([^,\]]*)\]?,.*')
  384. libx_re = re.compile('AX_CHECK_LIBRARY\(\s*\[?[^,\]]*\]?,\s*\[?([^,\]]*)\]?,\s*\[?([a-zA-Z0-9-]*)\]?,.*')
  385. progs_re = re.compile('_PROGS?\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*')
  386. dep_re = re.compile('([^ ><=]+)( [<>=]+ [^ ><=]+)?')
  387. ac_init_re = re.compile('AC_INIT\(\s*([^,]+),\s*([^,]+)[,)].*')
  388. am_init_re = re.compile('AM_INIT_AUTOMAKE\(\s*([^,]+),\s*([^,]+)[,)].*')
  389. define_re = re.compile('\s*(m4_)?define\(\s*([^,]+),\s*([^,]+)\)')
  390. version_re = re.compile('([0-9.]+)')
  391. defines = {}
  392. def subst_defines(value):
  393. newvalue = value
  394. for define, defval in defines.items():
  395. newvalue = newvalue.replace(define, defval)
  396. if newvalue != value:
  397. return subst_defines(newvalue)
  398. return value
  399. def process_value(value):
  400. value = value.replace('[', '').replace(']', '')
  401. if value.startswith('m4_esyscmd(') or value.startswith('m4_esyscmd_s('):
  402. cmd = subst_defines(value[value.index('(')+1:-1])
  403. try:
  404. if '|' in cmd:
  405. cmd = 'set -o pipefail; ' + cmd
  406. stdout, _ = bb.process.run(cmd, cwd=srctree, shell=True)
  407. ret = stdout.rstrip()
  408. except bb.process.ExecutionError as e:
  409. ret = ''
  410. elif value.startswith('m4_'):
  411. return None
  412. ret = subst_defines(value)
  413. if ret:
  414. ret = ret.strip('"\'')
  415. return ret
  416. # Since a configure.ac file is essentially a program, this is only ever going to be
  417. # a hack unfortunately; but it ought to be enough of an approximation
  418. if acfile:
  419. srcfiles = [acfile]
  420. else:
  421. srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in'])
  422. pcdeps = []
  423. libdeps = []
  424. deps = []
  425. unmapped = []
  426. RecipeHandler.load_binmap(tinfoil.config_data)
  427. def process_macro(keyword, value):
  428. for handler in handlers:
  429. if handler.process_macro(srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
  430. return
  431. logger.debug('Found keyword %s with value "%s"' % (keyword, value))
  432. if keyword == 'PKG_CHECK_MODULES':
  433. res = pkg_re.search(value)
  434. if res:
  435. res = dep_re.findall(res.group(1))
  436. if res:
  437. pcdeps.extend([x[0] for x in res])
  438. inherits.append('pkgconfig')
  439. elif keyword == 'PKG_CHECK_EXISTS':
  440. res = pkgce_re.search(value)
  441. if res:
  442. res = dep_re.findall(res.group(1))
  443. if res:
  444. pcdeps.extend([x[0] for x in res])
  445. inherits.append('pkgconfig')
  446. elif keyword in ('AM_GNU_GETTEXT', 'AM_GLIB_GNU_GETTEXT', 'GETTEXT_PACKAGE'):
  447. inherits.append('gettext')
  448. elif keyword in ('AC_PROG_INTLTOOL', 'IT_PROG_INTLTOOL'):
  449. deps.append('intltool-native')
  450. elif keyword == 'AM_PATH_GLIB_2_0':
  451. deps.append('glib-2.0')
  452. elif keyword in ('AC_CHECK_PROG', 'AC_PATH_PROG', 'AX_WITH_PROG'):
  453. res = progs_re.search(value)
  454. if res:
  455. for prog in shlex.split(res.group(1)):
  456. prog = prog.split()[0]
  457. for handler in handlers:
  458. if handler.process_prog(srctree, keyword, value, prog, deps, outlines, inherits, values):
  459. return
  460. progclass = progclassmap.get(prog, None)
  461. if progclass:
  462. inherits.append(progclass)
  463. else:
  464. progdep = RecipeHandler.recipebinmap.get(prog, None)
  465. if not progdep:
  466. progdep = progmap.get(prog, None)
  467. if progdep:
  468. deps.append(progdep)
  469. elif progdep is None:
  470. if not prog.startswith('$'):
  471. unmapped.append(prog)
  472. elif keyword == 'AC_CHECK_LIB':
  473. res = lib_re.search(value)
  474. if res:
  475. lib = res.group(1)
  476. if not lib.startswith('$'):
  477. libdeps.append(lib)
  478. elif keyword == 'AX_CHECK_LIBRARY':
  479. res = libx_re.search(value)
  480. if res:
  481. lib = res.group(2)
  482. if not lib.startswith('$'):
  483. header = res.group(1)
  484. libdeps.append((lib, header))
  485. elif keyword == 'AC_PATH_X':
  486. deps.append('libx11')
  487. elif keyword in ('AX_BOOST', 'BOOST_REQUIRE'):
  488. deps.append('boost')
  489. elif keyword in ('AC_PROG_LEX', 'AM_PROG_LEX', 'AX_PROG_FLEX'):
  490. deps.append('flex-native')
  491. elif keyword in ('AC_PROG_YACC', 'AX_PROG_BISON'):
  492. deps.append('bison-native')
  493. elif keyword == 'AX_CHECK_ZLIB':
  494. deps.append('zlib')
  495. elif keyword in ('AX_CHECK_OPENSSL', 'AX_LIB_CRYPTO'):
  496. deps.append('openssl')
  497. elif keyword == 'AX_LIB_CURL':
  498. deps.append('curl')
  499. elif keyword == 'AX_LIB_BEECRYPT':
  500. deps.append('beecrypt')
  501. elif keyword == 'AX_LIB_EXPAT':
  502. deps.append('expat')
  503. elif keyword == 'AX_LIB_GCRYPT':
  504. deps.append('libgcrypt')
  505. elif keyword == 'AX_LIB_NETTLE':
  506. deps.append('nettle')
  507. elif keyword == 'AX_LIB_READLINE':
  508. deps.append('readline')
  509. elif keyword == 'AX_LIB_SQLITE3':
  510. deps.append('sqlite3')
  511. elif keyword == 'AX_LIB_TAGLIB':
  512. deps.append('taglib')
  513. elif keyword in ['AX_PKG_SWIG', 'AC_PROG_SWIG']:
  514. deps.append('swig-native')
  515. elif keyword == 'AX_PROG_XSLTPROC':
  516. deps.append('libxslt-native')
  517. elif keyword in ['AC_PYTHON_DEVEL', 'AX_PYTHON_DEVEL', 'AM_PATH_PYTHON']:
  518. pythonclass = 'python3native'
  519. elif keyword == 'AX_WITH_CURSES':
  520. deps.append('ncurses')
  521. elif keyword == 'AX_PATH_BDB':
  522. deps.append('db')
  523. elif keyword == 'AX_PATH_LIB_PCRE':
  524. deps.append('libpcre')
  525. elif keyword == 'AC_INIT':
  526. if extravalues is not None:
  527. res = ac_init_re.match(value)
  528. if res:
  529. extravalues['PN'] = process_value(res.group(1))
  530. pv = process_value(res.group(2))
  531. if validate_pv(pv):
  532. extravalues['PV'] = pv
  533. elif keyword == 'AM_INIT_AUTOMAKE':
  534. if extravalues is not None:
  535. if 'PN' not in extravalues:
  536. res = am_init_re.match(value)
  537. if res:
  538. if res.group(1) != 'AC_PACKAGE_NAME':
  539. extravalues['PN'] = process_value(res.group(1))
  540. pv = process_value(res.group(2))
  541. if validate_pv(pv):
  542. extravalues['PV'] = pv
  543. elif keyword == 'define(':
  544. res = define_re.match(value)
  545. if res:
  546. key = res.group(2).strip('[]')
  547. value = process_value(res.group(3))
  548. if value is not None:
  549. defines[key] = value
  550. keywords = ['PKG_CHECK_MODULES',
  551. 'PKG_CHECK_EXISTS',
  552. 'AM_GNU_GETTEXT',
  553. 'AM_GLIB_GNU_GETTEXT',
  554. 'GETTEXT_PACKAGE',
  555. 'AC_PROG_INTLTOOL',
  556. 'IT_PROG_INTLTOOL',
  557. 'AM_PATH_GLIB_2_0',
  558. 'AC_CHECK_PROG',
  559. 'AC_PATH_PROG',
  560. 'AX_WITH_PROG',
  561. 'AC_CHECK_LIB',
  562. 'AX_CHECK_LIBRARY',
  563. 'AC_PATH_X',
  564. 'AX_BOOST',
  565. 'BOOST_REQUIRE',
  566. 'AC_PROG_LEX',
  567. 'AM_PROG_LEX',
  568. 'AX_PROG_FLEX',
  569. 'AC_PROG_YACC',
  570. 'AX_PROG_BISON',
  571. 'AX_CHECK_ZLIB',
  572. 'AX_CHECK_OPENSSL',
  573. 'AX_LIB_CRYPTO',
  574. 'AX_LIB_CURL',
  575. 'AX_LIB_BEECRYPT',
  576. 'AX_LIB_EXPAT',
  577. 'AX_LIB_GCRYPT',
  578. 'AX_LIB_NETTLE',
  579. 'AX_LIB_READLINE'
  580. 'AX_LIB_SQLITE3',
  581. 'AX_LIB_TAGLIB',
  582. 'AX_PKG_SWIG',
  583. 'AC_PROG_SWIG',
  584. 'AX_PROG_XSLTPROC',
  585. 'AC_PYTHON_DEVEL',
  586. 'AX_PYTHON_DEVEL',
  587. 'AM_PATH_PYTHON',
  588. 'AX_WITH_CURSES',
  589. 'AX_PATH_BDB',
  590. 'AX_PATH_LIB_PCRE',
  591. 'AC_INIT',
  592. 'AM_INIT_AUTOMAKE',
  593. 'define(',
  594. ]
  595. for handler in handlers:
  596. handler.extend_keywords(keywords)
  597. for srcfile in srcfiles:
  598. nesting = 0
  599. in_keyword = ''
  600. partial = ''
  601. with open(srcfile, 'r', errors='surrogateescape') as f:
  602. for line in f:
  603. if in_keyword:
  604. partial += ' ' + line.strip()
  605. if partial.endswith('\\'):
  606. partial = partial[:-1]
  607. nesting = nesting + line.count('(') - line.count(')')
  608. if nesting == 0:
  609. process_macro(in_keyword, partial)
  610. partial = ''
  611. in_keyword = ''
  612. else:
  613. for keyword in keywords:
  614. if keyword in line:
  615. nesting = line.count('(') - line.count(')')
  616. if nesting > 0:
  617. partial = line.strip()
  618. if partial.endswith('\\'):
  619. partial = partial[:-1]
  620. in_keyword = keyword
  621. else:
  622. process_macro(keyword, line.strip())
  623. break
  624. if in_keyword:
  625. process_macro(in_keyword, partial)
  626. if extravalues:
  627. for k,v in list(extravalues.items()):
  628. if v:
  629. if v.startswith('$') or v.startswith('@') or v.startswith('%'):
  630. del extravalues[k]
  631. else:
  632. extravalues[k] = v.strip('"\'').rstrip('()')
  633. if unmapped:
  634. outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmapped))))
  635. RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)
  636. for handler in handlers:
  637. handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)
  638. if inherits:
  639. values['inherit'] = ' '.join(list(set(inherits)))
  640. return values
  641. class AutotoolsExtensionHandler(object):
  642. '''Base class for Autotools extension handlers'''
  643. def process_macro(self, srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
  644. '''
  645. Handle a macro parsed out of an autotools file. Note that if you want this to be called
  646. for any macro other than the ones AutotoolsRecipeHandler already looks for, you'll need
  647. to add it to the keywords list in extend_keywords().
  648. Return True if you've completely handled the passed in macro, otherwise return False.
  649. '''
  650. return False
  651. def extend_keywords(self, keywords):
  652. '''Adds keywords to be recognised by the parser (so that you get a call to process_macro)'''
  653. return
  654. def process_prog(self, srctree, keyword, value, prog, deps, outlines, inherits, values):
  655. '''
  656. Handle an AC_PATH_PROG, AC_CHECK_PROG etc. line
  657. Return True if you've completely handled the passed in macro, otherwise return False.
  658. '''
  659. return False
  660. def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values):
  661. '''
  662. Apply any desired post-processing on the output
  663. '''
  664. return
  665. class MakefileRecipeHandler(RecipeHandler):
  666. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  667. if 'buildsystem' in handled:
  668. return False
  669. makefile = RecipeHandler.checkfiles(srctree, ['Makefile', 'makefile', 'GNUmakefile'])
  670. if makefile:
  671. lines_after.append('# NOTE: this is a Makefile-only piece of software, so we cannot generate much of the')
  672. lines_after.append('# recipe automatically - you will need to examine the Makefile yourself and ensure')
  673. lines_after.append('# that the appropriate arguments are passed in.')
  674. lines_after.append('')
  675. scanfile = os.path.join(srctree, 'configure.scan')
  676. skipscan = False
  677. try:
  678. stdout, stderr = bb.process.run('autoscan', cwd=srctree, shell=True)
  679. except bb.process.ExecutionError as e:
  680. skipscan = True
  681. if scanfile and os.path.exists(scanfile):
  682. values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, acfile=scanfile)
  683. classes.extend(values.pop('inherit', '').split())
  684. for var, value in values.items():
  685. if var == 'DEPENDS':
  686. lines_before.append('# NOTE: some of these dependencies may be optional, check the Makefile and/or upstream documentation')
  687. lines_before.append('%s = "%s"' % (var, value))
  688. lines_before.append('')
  689. for f in ['configure.scan', 'autoscan.log']:
  690. fp = os.path.join(srctree, f)
  691. if os.path.exists(fp):
  692. os.remove(fp)
  693. self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])
  694. func = []
  695. func.append('# You will almost certainly need to add additional arguments here')
  696. func.append('oe_runmake')
  697. self.genfunction(lines_after, 'do_compile', func)
  698. installtarget = True
  699. try:
  700. stdout, stderr = bb.process.run('make -n install', cwd=srctree, shell=True)
  701. except bb.process.ExecutionError as e:
  702. if e.exitcode != 1:
  703. installtarget = False
  704. func = []
  705. if installtarget:
  706. func.append('# This is a guess; additional arguments may be required')
  707. makeargs = ''
  708. with open(makefile[0], 'r', errors='surrogateescape') as f:
  709. for i in range(1, 100):
  710. if 'DESTDIR' in f.readline():
  711. makeargs += " 'DESTDIR=${D}'"
  712. break
  713. func.append('oe_runmake install%s' % makeargs)
  714. else:
  715. func.append('# NOTE: unable to determine what to put here - there is a Makefile but no')
  716. func.append('# target named "install", so you will need to define this yourself')
  717. self.genfunction(lines_after, 'do_install', func)
  718. handled.append('buildsystem')
  719. else:
  720. lines_after.append('# NOTE: no Makefile found, unable to determine what needs to be done')
  721. lines_after.append('')
  722. self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])
  723. self.genfunction(lines_after, 'do_compile', ['# Specify compilation commands here'])
  724. self.genfunction(lines_after, 'do_install', ['# Specify install commands here'])
  725. class VersionFileRecipeHandler(RecipeHandler):
  726. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  727. if 'PV' not in extravalues:
  728. # Look for a VERSION or version file containing a single line consisting
  729. # only of a version number
  730. filelist = RecipeHandler.checkfiles(srctree, ['VERSION', 'version'])
  731. version = None
  732. for fileitem in filelist:
  733. linecount = 0
  734. with open(fileitem, 'r', errors='surrogateescape') as f:
  735. for line in f:
  736. line = line.rstrip().strip('"\'')
  737. linecount += 1
  738. if line:
  739. if linecount > 1:
  740. version = None
  741. break
  742. else:
  743. if validate_pv(line):
  744. version = line
  745. if version:
  746. extravalues['PV'] = version
  747. break
  748. class SpecFileRecipeHandler(RecipeHandler):
  749. def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
  750. if 'PV' in extravalues and 'PN' in extravalues:
  751. return
  752. filelist = RecipeHandler.checkfiles(srctree, ['*.spec'], recursive=True)
  753. valuemap = {'Name': 'PN',
  754. 'Version': 'PV',
  755. 'Summary': 'SUMMARY',
  756. 'Url': 'HOMEPAGE',
  757. 'License': 'LICENSE'}
  758. foundvalues = {}
  759. for fileitem in filelist:
  760. linecount = 0
  761. with open(fileitem, 'r', errors='surrogateescape') as f:
  762. for line in f:
  763. for value, varname in valuemap.items():
  764. if line.startswith(value + ':') and not varname in foundvalues:
  765. foundvalues[varname] = line.split(':', 1)[1].strip()
  766. break
  767. if len(foundvalues) == len(valuemap):
  768. break
  769. # Drop values containing unexpanded RPM macros
  770. for k in list(foundvalues.keys()):
  771. if '%' in foundvalues[k]:
  772. del foundvalues[k]
  773. if 'PV' in foundvalues:
  774. if not validate_pv(foundvalues['PV']):
  775. del foundvalues['PV']
  776. license = foundvalues.pop('LICENSE', None)
  777. if license:
  778. liccomment = '# NOTE: spec file indicates the license may be "%s"' % license
  779. for i, line in enumerate(lines_before):
  780. if line.startswith('LICENSE ='):
  781. lines_before.insert(i, liccomment)
  782. break
  783. else:
  784. lines_before.append(liccomment)
  785. extravalues.update(foundvalues)
  786. def register_recipe_handlers(handlers):
  787. # Set priorities with some gaps so that other plugins can insert
  788. # their own handlers (so avoid changing these numbers)
  789. handlers.append((CmakeRecipeHandler(), 50))
  790. handlers.append((AutotoolsRecipeHandler(), 40))
  791. handlers.append((SconsRecipeHandler(), 30))
  792. handlers.append((QmakeRecipeHandler(), 20))
  793. handlers.append((MakefileRecipeHandler(), 10))
  794. handlers.append((VersionFileRecipeHandler(), -1))
  795. handlers.append((SpecFileRecipeHandler(), -1))