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