blob: e914e53aaba4f72c3c445708dbd877718e1402f2 [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())
Patrick Williamsc0f7c042017-02-23 20:41:17 -060047 for var, value in values.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050048 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)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600162 for fn, pn in RecipeHandler.recipecmakefilemap.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500163 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)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600176 with open(fn, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500177 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())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600351 for var, value in values.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500352 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
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600357 with open(conffile[0], 'r', errors='surrogateescape') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500358 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:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600367 with open(conffile[0], 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500368 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 Williamsc0f7c042017-02-23 20:41:17 -0600445 version_re = re.compile('([0-9.]+)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500446
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500447 defines = {}
448 def subst_defines(value):
449 newvalue = value
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600450 for define, defval in defines.items():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500451 newvalue = newvalue.replace(define, defval)
452 if newvalue != value:
453 return subst_defines(newvalue)
454 return value
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500455
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500456 def process_value(value):
457 value = value.replace('[', '').replace(']', '')
458 if value.startswith('m4_esyscmd(') or value.startswith('m4_esyscmd_s('):
459 cmd = subst_defines(value[value.index('(')+1:-1])
460 try:
461 if '|' in cmd:
462 cmd = 'set -o pipefail; ' + cmd
463 stdout, _ = bb.process.run(cmd, cwd=srctree, shell=True)
464 ret = stdout.rstrip()
465 except bb.process.ExecutionError as e:
466 ret = ''
467 elif value.startswith('m4_'):
468 return None
469 ret = subst_defines(value)
470 if ret:
471 ret = ret.strip('"\'')
472 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473
474 # Since a configure.ac file is essentially a program, this is only ever going to be
475 # a hack unfortunately; but it ought to be enough of an approximation
476 if acfile:
477 srcfiles = [acfile]
478 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500479 srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in'])
480
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500481 pcdeps = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500482 libdeps = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500483 deps = []
484 unmapped = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500485
486 RecipeHandler.load_binmap(tinfoil.config_data)
487
488 def process_macro(keyword, value):
489 for handler in handlers:
490 if handler.process_macro(srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
491 return
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600492 logger.debug('Found keyword %s with value "%s"' % (keyword, value))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500493 if keyword == 'PKG_CHECK_MODULES':
494 res = pkg_re.search(value)
495 if res:
496 res = dep_re.findall(res.group(1))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500497 if res:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500498 pcdeps.extend([x[0] for x in res])
499 inherits.append('pkgconfig')
500 elif keyword == 'PKG_CHECK_EXISTS':
501 res = pkgce_re.search(value)
502 if res:
503 res = dep_re.findall(res.group(1))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500504 if res:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500505 pcdeps.extend([x[0] for x in res])
506 inherits.append('pkgconfig')
507 elif keyword in ('AM_GNU_GETTEXT', 'AM_GLIB_GNU_GETTEXT', 'GETTEXT_PACKAGE'):
508 inherits.append('gettext')
509 elif keyword in ('AC_PROG_INTLTOOL', 'IT_PROG_INTLTOOL'):
510 deps.append('intltool-native')
511 elif keyword == 'AM_PATH_GLIB_2_0':
512 deps.append('glib-2.0')
513 elif keyword in ('AC_CHECK_PROG', 'AC_PATH_PROG', 'AX_WITH_PROG'):
514 res = progs_re.search(value)
515 if res:
516 for prog in shlex.split(res.group(1)):
517 prog = prog.split()[0]
518 for handler in handlers:
519 if handler.process_prog(srctree, keyword, value, prog, deps, outlines, inherits, values):
520 return
521 progclass = progclassmap.get(prog, None)
522 if progclass:
523 inherits.append(progclass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500524 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500525 progdep = RecipeHandler.recipebinmap.get(prog, None)
526 if not progdep:
527 progdep = progmap.get(prog, None)
528 if progdep:
529 deps.append(progdep)
530 elif progdep is None:
531 if not prog.startswith('$'):
532 unmapped.append(prog)
533 elif keyword == 'AC_CHECK_LIB':
534 res = lib_re.search(value)
535 if res:
536 lib = res.group(1)
537 if not lib.startswith('$'):
538 libdeps.append(lib)
539 elif keyword == 'AX_CHECK_LIBRARY':
540 res = libx_re.search(value)
541 if res:
542 lib = res.group(2)
543 if not lib.startswith('$'):
544 header = res.group(1)
545 libdeps.append((lib, header))
546 elif keyword == 'AC_PATH_X':
547 deps.append('libx11')
548 elif keyword in ('AX_BOOST', 'BOOST_REQUIRE'):
549 deps.append('boost')
550 elif keyword in ('AC_PROG_LEX', 'AM_PROG_LEX', 'AX_PROG_FLEX'):
551 deps.append('flex-native')
552 elif keyword in ('AC_PROG_YACC', 'AX_PROG_BISON'):
553 deps.append('bison-native')
554 elif keyword == 'AX_CHECK_ZLIB':
555 deps.append('zlib')
556 elif keyword in ('AX_CHECK_OPENSSL', 'AX_LIB_CRYPTO'):
557 deps.append('openssl')
558 elif keyword == 'AX_LIB_CURL':
559 deps.append('curl')
560 elif keyword == 'AX_LIB_BEECRYPT':
561 deps.append('beecrypt')
562 elif keyword == 'AX_LIB_EXPAT':
563 deps.append('expat')
564 elif keyword == 'AX_LIB_GCRYPT':
565 deps.append('libgcrypt')
566 elif keyword == 'AX_LIB_NETTLE':
567 deps.append('nettle')
568 elif keyword == 'AX_LIB_READLINE':
569 deps.append('readline')
570 elif keyword == 'AX_LIB_SQLITE3':
571 deps.append('sqlite3')
572 elif keyword == 'AX_LIB_TAGLIB':
573 deps.append('taglib')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600574 elif keyword in ['AX_PKG_SWIG', 'AC_PROG_SWIG']:
575 deps.append('swig-native')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500576 elif keyword == 'AX_PROG_XSLTPROC':
577 deps.append('libxslt-native')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600578 elif keyword in ['AC_PYTHON_DEVEL', 'AX_PYTHON_DEVEL', 'AM_PATH_PYTHON']:
579 pythonclass = 'pythonnative'
580 res = version_re.search(value)
581 if res:
582 if res.group(1).startswith('3'):
583 pythonclass = 'python3native'
584 # Avoid replacing python3native with pythonnative
585 if not pythonclass in inherits and not 'python3native' in inherits:
586 if 'pythonnative' in inherits:
587 inherits.remove('pythonnative')
588 inherits.append(pythonclass)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500589 elif keyword == 'AX_WITH_CURSES':
590 deps.append('ncurses')
591 elif keyword == 'AX_PATH_BDB':
592 deps.append('db')
593 elif keyword == 'AX_PATH_LIB_PCRE':
594 deps.append('libpcre')
595 elif keyword == 'AC_INIT':
596 if extravalues is not None:
597 res = ac_init_re.match(value)
598 if res:
599 extravalues['PN'] = process_value(res.group(1))
600 pv = process_value(res.group(2))
601 if validate_pv(pv):
602 extravalues['PV'] = pv
603 elif keyword == 'AM_INIT_AUTOMAKE':
604 if extravalues is not None:
605 if 'PN' not in extravalues:
606 res = am_init_re.match(value)
607 if res:
608 if res.group(1) != 'AC_PACKAGE_NAME':
609 extravalues['PN'] = process_value(res.group(1))
610 pv = process_value(res.group(2))
611 if validate_pv(pv):
612 extravalues['PV'] = pv
613 elif keyword == 'define(':
614 res = define_re.match(value)
615 if res:
616 key = res.group(2).strip('[]')
617 value = process_value(res.group(3))
618 if value is not None:
619 defines[key] = value
620
621 keywords = ['PKG_CHECK_MODULES',
622 'PKG_CHECK_EXISTS',
623 'AM_GNU_GETTEXT',
624 'AM_GLIB_GNU_GETTEXT',
625 'GETTEXT_PACKAGE',
626 'AC_PROG_INTLTOOL',
627 'IT_PROG_INTLTOOL',
628 'AM_PATH_GLIB_2_0',
629 'AC_CHECK_PROG',
630 'AC_PATH_PROG',
631 'AX_WITH_PROG',
632 'AC_CHECK_LIB',
633 'AX_CHECK_LIBRARY',
634 'AC_PATH_X',
635 'AX_BOOST',
636 'BOOST_REQUIRE',
637 'AC_PROG_LEX',
638 'AM_PROG_LEX',
639 'AX_PROG_FLEX',
640 'AC_PROG_YACC',
641 'AX_PROG_BISON',
642 'AX_CHECK_ZLIB',
643 'AX_CHECK_OPENSSL',
644 'AX_LIB_CRYPTO',
645 'AX_LIB_CURL',
646 'AX_LIB_BEECRYPT',
647 'AX_LIB_EXPAT',
648 'AX_LIB_GCRYPT',
649 'AX_LIB_NETTLE',
650 'AX_LIB_READLINE'
651 'AX_LIB_SQLITE3',
652 'AX_LIB_TAGLIB',
653 'AX_PKG_SWIG',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600654 'AC_PROG_SWIG',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500655 'AX_PROG_XSLTPROC',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600656 'AC_PYTHON_DEVEL',
657 'AX_PYTHON_DEVEL',
658 'AM_PATH_PYTHON',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500659 'AX_WITH_CURSES',
660 'AX_PATH_BDB',
661 'AX_PATH_LIB_PCRE',
662 'AC_INIT',
663 'AM_INIT_AUTOMAKE',
664 'define(',
665 ]
666
667 for handler in handlers:
668 handler.extend_keywords(keywords)
669
670 for srcfile in srcfiles:
671 nesting = 0
672 in_keyword = ''
673 partial = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600674 with open(srcfile, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500675 for line in f:
676 if in_keyword:
677 partial += ' ' + line.strip()
678 if partial.endswith('\\'):
679 partial = partial[:-1]
680 nesting = nesting + line.count('(') - line.count(')')
681 if nesting == 0:
682 process_macro(in_keyword, partial)
683 partial = ''
684 in_keyword = ''
685 else:
686 for keyword in keywords:
687 if keyword in line:
688 nesting = line.count('(') - line.count(')')
689 if nesting > 0:
690 partial = line.strip()
691 if partial.endswith('\\'):
692 partial = partial[:-1]
693 in_keyword = keyword
694 else:
695 process_macro(keyword, line.strip())
696 break
697
698 if in_keyword:
699 process_macro(in_keyword, partial)
700
701 if extravalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600702 for k,v in list(extravalues.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500703 if v:
704 if v.startswith('$') or v.startswith('@') or v.startswith('%'):
705 del extravalues[k]
706 else:
707 extravalues[k] = v.strip('"\'').rstrip('()')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500708
709 if unmapped:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500710 outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmapped))))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500712 RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500714 for handler in handlers:
715 handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716
717 if inherits:
718 values['inherit'] = ' '.join(list(set(inherits)))
719
720 return values
721
722
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500723class AutotoolsExtensionHandler(object):
724 '''Base class for Autotools extension handlers'''
725 def process_macro(self, srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
726 '''
727 Handle a macro parsed out of an autotools file. Note that if you want this to be called
728 for any macro other than the ones AutotoolsRecipeHandler already looks for, you'll need
729 to add it to the keywords list in extend_keywords().
730 Return True if you've completely handled the passed in macro, otherwise return False.
731 '''
732 return False
733
734 def extend_keywords(self, keywords):
735 '''Adds keywords to be recognised by the parser (so that you get a call to process_macro)'''
736 return
737
738 def process_prog(self, srctree, keyword, value, prog, deps, outlines, inherits, values):
739 '''
740 Handle an AC_PATH_PROG, AC_CHECK_PROG etc. line
741 Return True if you've completely handled the passed in macro, otherwise return False.
742 '''
743 return False
744
745 def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values):
746 '''
747 Apply any desired post-processing on the output
748 '''
749 return
750
751
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500752class MakefileRecipeHandler(RecipeHandler):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500753 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500754 if 'buildsystem' in handled:
755 return False
756
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600757 makefile = RecipeHandler.checkfiles(srctree, ['Makefile', 'makefile', 'GNUmakefile'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500758 if makefile:
759 lines_after.append('# NOTE: this is a Makefile-only piece of software, so we cannot generate much of the')
760 lines_after.append('# recipe automatically - you will need to examine the Makefile yourself and ensure')
761 lines_after.append('# that the appropriate arguments are passed in.')
762 lines_after.append('')
763
764 scanfile = os.path.join(srctree, 'configure.scan')
765 skipscan = False
766 try:
767 stdout, stderr = bb.process.run('autoscan', cwd=srctree, shell=True)
768 except bb.process.ExecutionError as e:
769 skipscan = True
770 if scanfile and os.path.exists(scanfile):
771 values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, acfile=scanfile)
772 classes.extend(values.pop('inherit', '').split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600773 for var, value in values.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500774 if var == 'DEPENDS':
775 lines_before.append('# NOTE: some of these dependencies may be optional, check the Makefile and/or upstream documentation')
776 lines_before.append('%s = "%s"' % (var, value))
777 lines_before.append('')
778 for f in ['configure.scan', 'autoscan.log']:
779 fp = os.path.join(srctree, f)
780 if os.path.exists(fp):
781 os.remove(fp)
782
783 self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])
784
785 func = []
786 func.append('# You will almost certainly need to add additional arguments here')
787 func.append('oe_runmake')
788 self.genfunction(lines_after, 'do_compile', func)
789
790 installtarget = True
791 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500792 stdout, stderr = bb.process.run('make -n install', cwd=srctree, shell=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500793 except bb.process.ExecutionError as e:
794 if e.exitcode != 1:
795 installtarget = False
796 func = []
797 if installtarget:
798 func.append('# This is a guess; additional arguments may be required')
799 makeargs = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600800 with open(makefile[0], 'r', errors='surrogateescape') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500801 for i in range(1, 100):
802 if 'DESTDIR' in f.readline():
803 makeargs += " 'DESTDIR=${D}'"
804 break
805 func.append('oe_runmake install%s' % makeargs)
806 else:
807 func.append('# NOTE: unable to determine what to put here - there is a Makefile but no')
808 func.append('# target named "install", so you will need to define this yourself')
809 self.genfunction(lines_after, 'do_install', func)
810
811 handled.append('buildsystem')
812 else:
813 lines_after.append('# NOTE: no Makefile found, unable to determine what needs to be done')
814 lines_after.append('')
815 self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])
816 self.genfunction(lines_after, 'do_compile', ['# Specify compilation commands here'])
817 self.genfunction(lines_after, 'do_install', ['# Specify install commands here'])
818
819
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500820class VersionFileRecipeHandler(RecipeHandler):
821 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
822 if 'PV' not in extravalues:
823 # Look for a VERSION or version file containing a single line consisting
824 # only of a version number
825 filelist = RecipeHandler.checkfiles(srctree, ['VERSION', 'version'])
826 version = None
827 for fileitem in filelist:
828 linecount = 0
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600829 with open(fileitem, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500830 for line in f:
831 line = line.rstrip().strip('"\'')
832 linecount += 1
833 if line:
834 if linecount > 1:
835 version = None
836 break
837 else:
838 if validate_pv(line):
839 version = line
840 if version:
841 extravalues['PV'] = version
842 break
843
844
845class SpecFileRecipeHandler(RecipeHandler):
846 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
847 if 'PV' in extravalues and 'PN' in extravalues:
848 return
849 filelist = RecipeHandler.checkfiles(srctree, ['*.spec'], recursive=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600850 valuemap = {'Name': 'PN',
851 'Version': 'PV',
852 'Summary': 'SUMMARY',
853 'Url': 'HOMEPAGE',
854 'License': 'LICENSE'}
855 foundvalues = {}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500856 for fileitem in filelist:
857 linecount = 0
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600858 with open(fileitem, 'r', errors='surrogateescape') as f:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500859 for line in f:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600860 for value, varname in valuemap.items():
861 if line.startswith(value + ':') and not varname in foundvalues:
862 foundvalues[varname] = line.split(':', 1)[1].strip()
863 break
864 if len(foundvalues) == len(valuemap):
865 break
866 if 'PV' in foundvalues:
867 if not validate_pv(foundvalues['PV']):
868 del foundvalues['PV']
869 license = foundvalues.pop('LICENSE', None)
870 if license:
871 liccomment = '# NOTE: spec file indicates the license may be "%s"' % license
872 for i, line in enumerate(lines_before):
873 if line.startswith('LICENSE ='):
874 lines_before.insert(i, liccomment)
875 break
876 else:
877 lines_before.append(liccomment)
878 extravalues.update(foundvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500879
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500880def register_recipe_handlers(handlers):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500881 # Set priorities with some gaps so that other plugins can insert
882 # their own handlers (so avoid changing these numbers)
883 handlers.append((CmakeRecipeHandler(), 50))
884 handlers.append((AutotoolsRecipeHandler(), 40))
885 handlers.append((SconsRecipeHandler(), 30))
886 handlers.append((QmakeRecipeHandler(), 20))
887 handlers.append((MakefileRecipeHandler(), 10))
888 handlers.append((VersionFileRecipeHandler(), -1))
889 handlers.append((SpecFileRecipeHandler(), -1))