create_buildsys.py 38 KB

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