blob: 4743c740cf919d5e7aa9c7c4ae4de4124b9fce15 [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001# Recipe creation tool - create command build system handlers
2#
3# Copyright (C) 2014-2016 Intel Corporation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18import re
19import logging
20import glob
21from recipetool.create import RecipeHandler, validate_pv
22
23logger = logging.getLogger('recipetool')
24
25tinfoil = None
26plugins = 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
32
33def tinfoil_init(instance):
34 global tinfoil
35 tinfoil = instance
36
37
38class CmakeRecipeHandler(RecipeHandler):
39 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
40 if 'buildsystem' in handled:
41 return False
42
43 if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']):
44 classes.append('cmake')
45 values = CmakeRecipeHandler.extract_cmake_deps(lines_before, srctree, extravalues)
46 classes.extend(values.pop('inherit', '').split())
47 for var, value in values.items():
48 lines_before.append('%s = "%s"' % (var, value))
49 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
56 @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.items():
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', errors='surrogateescape') 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
314class SconsRecipeHandler(RecipeHandler):
315 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
316 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
328
329class QmakeRecipeHandler(RecipeHandler):
330 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
331 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
340
341class AutotoolsRecipeHandler(RecipeHandler):
342 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
343 if 'buildsystem' in handled:
344 return False
345
346 autoconf = False
347 if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']):
348 autoconf = True
349 values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, extravalues)
350 classes.extend(values.pop('inherit', '').split())
351 for var, value in values.items():
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', errors='surrogateescape') as f:
358 for i in range(1, 10):
359 if 'Generated by GNU Autoconf' in f.readline():
360 autoconf = True
361 break
362
363 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', errors='surrogateescape') 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
379 if autoconf:
380 lines_before.append('')
381 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
394 def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None):
395 import shlex
396
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)
403
404 values = {}
405 inherits = []
406
407 # Hardcoded map, we also use a dynamic one based on what's in the sysroot
408 progmap = {'flex': 'flex-native',
409 'bison': 'bison-native',
410 '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 }
428 progclassmap = {'gconftool-2': 'gconf',
429 'pkg-config': 'pkgconfig',
430 'python': 'pythonnative',
431 'python3': 'python3native',
432 'perl': 'perlnative',
433 'makeinfo': 'texinfo',
434 }
435
436 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*\[?([^,\]]*)\]?[),].*')
441 dep_re = re.compile('([^ ><=]+)( [<>=]+ [^ ><=]+)?')
442 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*([^,]+)\)')
445 version_re = re.compile('([0-9.]+)')
446
447 defines = {}
448 def subst_defines(value):
449 newvalue = value
450 for define, defval in defines.items():
451 newvalue = newvalue.replace(define, defval)
452 if newvalue != value:
453 return subst_defines(newvalue)
454 return value
455
456 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
473
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:
479 srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in'])
480
481 pcdeps = []
482 libdeps = []
483 deps = []
484 unmapped = []
485
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
492 logger.debug('Found keyword %s with value "%s"' % (keyword, value))
493 if keyword == 'PKG_CHECK_MODULES':
494 res = pkg_re.search(value)
495 if res:
496 res = dep_re.findall(res.group(1))
497 if res:
498 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))
504 if res:
505 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)
524 else:
525 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')
574 elif keyword in ['AX_PKG_SWIG', 'AC_PROG_SWIG']:
575 deps.append('swig-native')
576 elif keyword == 'AX_PROG_XSLTPROC':
577 deps.append('libxslt-native')
578 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)
589 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',
654 'AC_PROG_SWIG',
655 'AX_PROG_XSLTPROC',
656 'AC_PYTHON_DEVEL',
657 'AX_PYTHON_DEVEL',
658 'AM_PATH_PYTHON',
659 '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 = ''
674 with open(srcfile, 'r', errors='surrogateescape') as f:
675 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:
702 for k,v in list(extravalues.items()):
703 if v:
704 if v.startswith('$') or v.startswith('@') or v.startswith('%'):
705 del extravalues[k]
706 else:
707 extravalues[k] = v.strip('"\'').rstrip('()')
708
709 if unmapped:
710 outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmapped))))
711
712 RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)
713
714 for handler in handlers:
715 handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)
716
717 if inherits:
718 values['inherit'] = ' '.join(list(set(inherits)))
719
720 return values
721
722
723class 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
752class MakefileRecipeHandler(RecipeHandler):
753 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
754 if 'buildsystem' in handled:
755 return False
756
757 makefile = RecipeHandler.checkfiles(srctree, ['Makefile', 'makefile', 'GNUmakefile'])
758 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())
773 for var, value in values.items():
774 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:
792 stdout, stderr = bb.process.run('make -n install', cwd=srctree, shell=True)
793 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 = ''
800 with open(makefile[0], 'r', errors='surrogateescape') as f:
801 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
820class 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
829 with open(fileitem, 'r', errors='surrogateescape') as f:
830 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)
850 valuemap = {'Name': 'PN',
851 'Version': 'PV',
852 'Summary': 'SUMMARY',
853 'Url': 'HOMEPAGE',
854 'License': 'LICENSE'}
855 foundvalues = {}
856 for fileitem in filelist:
857 linecount = 0
858 with open(fileitem, 'r', errors='surrogateescape') as f:
859 for line in f:
860 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 # Drop values containing unexpanded RPM macros
867 for k in list(foundvalues.keys()):
868 if '%' in foundvalues[k]:
869 del foundvalues[k]
870 if 'PV' in foundvalues:
871 if not validate_pv(foundvalues['PV']):
872 del foundvalues['PV']
873 license = foundvalues.pop('LICENSE', None)
874 if license:
875 liccomment = '# NOTE: spec file indicates the license may be "%s"' % license
876 for i, line in enumerate(lines_before):
877 if line.startswith('LICENSE ='):
878 lines_before.insert(i, liccomment)
879 break
880 else:
881 lines_before.append(liccomment)
882 extravalues.update(foundvalues)
883
884def register_recipe_handlers(handlers):
885 # Set priorities with some gaps so that other plugins can insert
886 # their own handlers (so avoid changing these numbers)
887 handlers.append((CmakeRecipeHandler(), 50))
888 handlers.append((AutotoolsRecipeHandler(), 40))
889 handlers.append((SconsRecipeHandler(), 30))
890 handlers.append((QmakeRecipeHandler(), 20))
891 handlers.append((MakefileRecipeHandler(), 10))
892 handlers.append((VersionFileRecipeHandler(), -1))
893 handlers.append((SpecFileRecipeHandler(), -1))