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