1# Recipe creation tool - create command build system handlers 2# 3# Copyright (C) 2014-2016 Intel Corporation 4# 5# SPDX-License-Identifier: GPL-2.0-only 6# 7 8import os 9import re 10import logging 11from recipetool.create import RecipeHandler, validate_pv 12 13logger = logging.getLogger('recipetool') 14 15tinfoil = None 16plugins = 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 22 23def tinfoil_init(instance): 24 global tinfoil 25 tinfoil = instance 26 27 28class CmakeRecipeHandler(RecipeHandler): 29 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): 30 if 'buildsystem' in handled: 31 return False 32 33 if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']): 34 classes.append('cmake') 35 values = CmakeRecipeHandler.extract_cmake_deps(lines_before, srctree, extravalues) 36 classes.extend(values.pop('inherit', '').split()) 37 for var, value in values.items(): 38 lines_before.append('%s = "%s"' % (var, value)) 39 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 46 @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(r'project\s*\(([^)]*)\)', re.IGNORECASE) 141 pkgcm_re = re.compile(r'pkg_check_modules\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?\s+([^)\s]+)\s*\)', re.IGNORECASE) 142 pkgsm_re = re.compile(r'pkg_search_module\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?((\s+[^)\s]+)+)\s*\)', re.IGNORECASE) 143 findpackage_re = re.compile(r'find_package\s*\(\s*([a-zA-Z0-9-_]+)\s*.*', re.IGNORECASE) 144 findlibrary_re = re.compile(r'find_library\s*\(\s*[a-zA-Z0-9-_]+\s*(NAMES\s+)?([a-zA-Z0-9-_ ]+)\s*.*') 145 checklib_re = re.compile(r'check_library_exists\s*\(\s*([^\s)]+)\s*.*', re.IGNORECASE) 146 include_re = re.compile(r'include\s*\(\s*([^)\s]*)\s*\)', re.IGNORECASE) 147 subdir_re = re.compile(r'add_subdirectory\s*\(\s*([^)\s]*)\s*([^)\s]*)\s*\)', re.IGNORECASE) 148 dep_re = re.compile(r'([^ ><=]+)( *[<>=]+ *[^ ><=]+)?') 149 150 def find_cmake_package(pkg): 151 RecipeHandler.load_devel_filemap(tinfoil.config_data) 152 for fn, pn in RecipeHandler.recipecmakefilemap.items(): 153 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) 166 with open(fn, 'r', errors='surrogateescape') as f: 167 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('python3native') 230 elif pkg == 'PythonLibs': 231 inherits.append('python3-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 304class SconsRecipeHandler(RecipeHandler): 305 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): 306 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 318 319class QmakeRecipeHandler(RecipeHandler): 320 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): 321 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 330 331class AutotoolsRecipeHandler(RecipeHandler): 332 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): 333 if 'buildsystem' in handled: 334 return False 335 336 autoconf = False 337 if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']): 338 autoconf = True 339 values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, extravalues) 340 classes.extend(values.pop('inherit', '').split()) 341 for var, value in values.items(): 342 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 347 with open(conffile[0], 'r', errors='surrogateescape') as f: 348 for i in range(1, 10): 349 if 'Generated by GNU Autoconf' in f.readline(): 350 autoconf = True 351 break 352 353 if autoconf and not ('PV' in extravalues and 'PN' in extravalues): 354 # Last resort 355 conffile = RecipeHandler.checkfiles(srctree, ['configure']) 356 if conffile: 357 with open(conffile[0], 'r', errors='surrogateescape') as f: 358 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 369 if autoconf: 370 lines_before.append('') 371 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 384 def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None): 385 import shlex 386 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) 393 394 values = {} 395 inherits = [] 396 397 # Hardcoded map, we also use a dynamic one based on what's in the sysroot 398 progmap = {'flex': 'flex-native', 399 'bison': 'bison-native', 400 '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 } 418 progclassmap = {'gconftool-2': 'gconf', 419 'pkg-config': 'pkgconfig', 420 'python': 'python3native', 421 'python3': 'python3native', 422 'perl': 'perlnative', 423 'makeinfo': 'texinfo', 424 } 425 426 pkg_re = re.compile(r'PKG_CHECK_MODULES\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*') 427 pkgce_re = re.compile(r'PKG_CHECK_EXISTS\(\s*\[?([^,\]]*)\]?[),].*') 428 lib_re = re.compile(r'AC_CHECK_LIB\(\s*\[?([^,\]]*)\]?,.*') 429 libx_re = re.compile(r'AX_CHECK_LIBRARY\(\s*\[?[^,\]]*\]?,\s*\[?([^,\]]*)\]?,\s*\[?([a-zA-Z0-9-]*)\]?,.*') 430 progs_re = re.compile(r'_PROGS?\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*') 431 dep_re = re.compile(r'([^ ><=]+)( [<>=]+ [^ ><=]+)?') 432 ac_init_re = re.compile(r'AC_INIT\(\s*([^,]+),\s*([^,]+)[,)].*') 433 am_init_re = re.compile(r'AM_INIT_AUTOMAKE\(\s*([^,]+),\s*([^,]+)[,)].*') 434 define_re = re.compile(r'\s*(m4_)?define\(\s*([^,]+),\s*([^,]+)\)') 435 version_re = re.compile(r'([0-9.]+)') 436 437 defines = {} 438 def subst_defines(value): 439 newvalue = value 440 for define, defval in defines.items(): 441 newvalue = newvalue.replace(define, defval) 442 if newvalue != value: 443 return subst_defines(newvalue) 444 return value 445 446 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 463 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: 469 srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in']) 470 471 pcdeps = [] 472 libdeps = [] 473 deps = [] 474 unmapped = [] 475 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 482 logger.debug('Found keyword %s with value "%s"' % (keyword, value)) 483 if keyword == 'PKG_CHECK_MODULES': 484 res = pkg_re.search(value) 485 if res: 486 res = dep_re.findall(res.group(1)) 487 if res: 488 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)) 494 if res: 495 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) 514 else: 515 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 in ('AX_LIB_CURL', 'LIBCURL_CHECK_CONFIG'): 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') 564 elif keyword in ['AX_PKG_SWIG', 'AC_PROG_SWIG']: 565 deps.append('swig-native') 566 elif keyword == 'AX_PROG_XSLTPROC': 567 deps.append('libxslt-native') 568 elif keyword in ['AC_PYTHON_DEVEL', 'AX_PYTHON_DEVEL', 'AM_PATH_PYTHON']: 569 pythonclass = 'python3native' 570 elif keyword == 'AX_WITH_CURSES': 571 deps.append('ncurses') 572 elif keyword == 'AX_PATH_BDB': 573 deps.append('db') 574 elif keyword == 'AX_PATH_LIB_PCRE': 575 deps.append('libpcre') 576 elif keyword == 'AC_INIT': 577 if extravalues is not None: 578 res = ac_init_re.match(value) 579 if res: 580 extravalues['PN'] = process_value(res.group(1)) 581 pv = process_value(res.group(2)) 582 if validate_pv(pv): 583 extravalues['PV'] = pv 584 elif keyword == 'AM_INIT_AUTOMAKE': 585 if extravalues is not None: 586 if 'PN' not in extravalues: 587 res = am_init_re.match(value) 588 if res: 589 if res.group(1) != 'AC_PACKAGE_NAME': 590 extravalues['PN'] = process_value(res.group(1)) 591 pv = process_value(res.group(2)) 592 if validate_pv(pv): 593 extravalues['PV'] = pv 594 elif keyword == 'define(': 595 res = define_re.match(value) 596 if res: 597 key = res.group(2).strip('[]') 598 value = process_value(res.group(3)) 599 if value is not None: 600 defines[key] = value 601 602 keywords = ['PKG_CHECK_MODULES', 603 'PKG_CHECK_EXISTS', 604 'AM_GNU_GETTEXT', 605 'AM_GLIB_GNU_GETTEXT', 606 'GETTEXT_PACKAGE', 607 'AC_PROG_INTLTOOL', 608 'IT_PROG_INTLTOOL', 609 'AM_PATH_GLIB_2_0', 610 'AC_CHECK_PROG', 611 'AC_PATH_PROG', 612 'AX_WITH_PROG', 613 'AC_CHECK_LIB', 614 'AX_CHECK_LIBRARY', 615 'AC_PATH_X', 616 'AX_BOOST', 617 'BOOST_REQUIRE', 618 'AC_PROG_LEX', 619 'AM_PROG_LEX', 620 'AX_PROG_FLEX', 621 'AC_PROG_YACC', 622 'AX_PROG_BISON', 623 'AX_CHECK_ZLIB', 624 'AX_CHECK_OPENSSL', 625 'AX_LIB_CRYPTO', 626 'AX_LIB_CURL', 627 'LIBCURL_CHECK_CONFIG', 628 'AX_LIB_BEECRYPT', 629 'AX_LIB_EXPAT', 630 'AX_LIB_GCRYPT', 631 'AX_LIB_NETTLE', 632 'AX_LIB_READLINE' 633 'AX_LIB_SQLITE3', 634 'AX_LIB_TAGLIB', 635 'AX_PKG_SWIG', 636 'AC_PROG_SWIG', 637 'AX_PROG_XSLTPROC', 638 'AC_PYTHON_DEVEL', 639 'AX_PYTHON_DEVEL', 640 'AM_PATH_PYTHON', 641 'AX_WITH_CURSES', 642 'AX_PATH_BDB', 643 'AX_PATH_LIB_PCRE', 644 'AC_INIT', 645 'AM_INIT_AUTOMAKE', 646 'define(', 647 ] 648 649 for handler in handlers: 650 handler.extend_keywords(keywords) 651 652 for srcfile in srcfiles: 653 nesting = 0 654 in_keyword = '' 655 partial = '' 656 with open(srcfile, 'r', errors='surrogateescape') as f: 657 for line in f: 658 if in_keyword: 659 partial += ' ' + line.strip() 660 if partial.endswith('\\'): 661 partial = partial[:-1] 662 nesting = nesting + line.count('(') - line.count(')') 663 if nesting == 0: 664 process_macro(in_keyword, partial) 665 partial = '' 666 in_keyword = '' 667 else: 668 for keyword in keywords: 669 if keyword in line: 670 nesting = line.count('(') - line.count(')') 671 if nesting > 0: 672 partial = line.strip() 673 if partial.endswith('\\'): 674 partial = partial[:-1] 675 in_keyword = keyword 676 else: 677 process_macro(keyword, line.strip()) 678 break 679 680 if in_keyword: 681 process_macro(in_keyword, partial) 682 683 if extravalues: 684 for k,v in list(extravalues.items()): 685 if v: 686 if v.startswith('$') or v.startswith('@') or v.startswith('%'): 687 del extravalues[k] 688 else: 689 extravalues[k] = v.strip('"\'').rstrip('()') 690 691 if unmapped: 692 outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmapped)))) 693 694 RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data) 695 696 for handler in handlers: 697 handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values) 698 699 if inherits: 700 values['inherit'] = ' '.join(list(set(inherits))) 701 702 return values 703 704 705class AutotoolsExtensionHandler(object): 706 '''Base class for Autotools extension handlers''' 707 def process_macro(self, srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values): 708 ''' 709 Handle a macro parsed out of an autotools file. Note that if you want this to be called 710 for any macro other than the ones AutotoolsRecipeHandler already looks for, you'll need 711 to add it to the keywords list in extend_keywords(). 712 Return True if you've completely handled the passed in macro, otherwise return False. 713 ''' 714 return False 715 716 def extend_keywords(self, keywords): 717 '''Adds keywords to be recognised by the parser (so that you get a call to process_macro)''' 718 return 719 720 def process_prog(self, srctree, keyword, value, prog, deps, outlines, inherits, values): 721 ''' 722 Handle an AC_PATH_PROG, AC_CHECK_PROG etc. line 723 Return True if you've completely handled the passed in macro, otherwise return False. 724 ''' 725 return False 726 727 def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values): 728 ''' 729 Apply any desired post-processing on the output 730 ''' 731 return 732 733 734class MakefileRecipeHandler(RecipeHandler): 735 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): 736 if 'buildsystem' in handled: 737 return False 738 739 makefile = RecipeHandler.checkfiles(srctree, ['Makefile', 'makefile', 'GNUmakefile']) 740 if makefile: 741 lines_after.append('# NOTE: this is a Makefile-only piece of software, so we cannot generate much of the') 742 lines_after.append('# recipe automatically - you will need to examine the Makefile yourself and ensure') 743 lines_after.append('# that the appropriate arguments are passed in.') 744 lines_after.append('') 745 746 scanfile = os.path.join(srctree, 'configure.scan') 747 skipscan = False 748 try: 749 stdout, stderr = bb.process.run('autoscan', cwd=srctree, shell=True) 750 except bb.process.ExecutionError as e: 751 skipscan = True 752 if scanfile and os.path.exists(scanfile): 753 values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, acfile=scanfile) 754 classes.extend(values.pop('inherit', '').split()) 755 for var, value in values.items(): 756 if var == 'DEPENDS': 757 lines_before.append('# NOTE: some of these dependencies may be optional, check the Makefile and/or upstream documentation') 758 lines_before.append('%s = "%s"' % (var, value)) 759 lines_before.append('') 760 for f in ['configure.scan', 'autoscan.log']: 761 fp = os.path.join(srctree, f) 762 if os.path.exists(fp): 763 os.remove(fp) 764 765 self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here']) 766 767 func = [] 768 func.append('# You will almost certainly need to add additional arguments here') 769 func.append('oe_runmake') 770 self.genfunction(lines_after, 'do_compile', func) 771 772 installtarget = True 773 try: 774 stdout, stderr = bb.process.run('make -n install', cwd=srctree, shell=True) 775 except bb.process.ExecutionError as e: 776 if e.exitcode != 1: 777 installtarget = False 778 func = [] 779 if installtarget: 780 func.append('# This is a guess; additional arguments may be required') 781 makeargs = '' 782 with open(makefile[0], 'r', errors='surrogateescape') as f: 783 for i in range(1, 100): 784 if 'DESTDIR' in f.readline(): 785 makeargs += " 'DESTDIR=${D}'" 786 break 787 func.append('oe_runmake install%s' % makeargs) 788 else: 789 func.append('# NOTE: unable to determine what to put here - there is a Makefile but no') 790 func.append('# target named "install", so you will need to define this yourself') 791 self.genfunction(lines_after, 'do_install', func) 792 793 handled.append('buildsystem') 794 else: 795 lines_after.append('# NOTE: no Makefile found, unable to determine what needs to be done') 796 lines_after.append('') 797 self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here']) 798 self.genfunction(lines_after, 'do_compile', ['# Specify compilation commands here']) 799 self.genfunction(lines_after, 'do_install', ['# Specify install commands here']) 800 801 802class VersionFileRecipeHandler(RecipeHandler): 803 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): 804 if 'PV' not in extravalues: 805 # Look for a VERSION or version file containing a single line consisting 806 # only of a version number 807 filelist = RecipeHandler.checkfiles(srctree, ['VERSION', 'version']) 808 version = None 809 for fileitem in filelist: 810 linecount = 0 811 with open(fileitem, 'r', errors='surrogateescape') as f: 812 for line in f: 813 line = line.rstrip().strip('"\'') 814 linecount += 1 815 if line: 816 if linecount > 1: 817 version = None 818 break 819 else: 820 if validate_pv(line): 821 version = line 822 if version: 823 extravalues['PV'] = version 824 break 825 826 827class SpecFileRecipeHandler(RecipeHandler): 828 def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): 829 if 'PV' in extravalues and 'PN' in extravalues: 830 return 831 filelist = RecipeHandler.checkfiles(srctree, ['*.spec'], recursive=True) 832 valuemap = {'Name': 'PN', 833 'Version': 'PV', 834 'Summary': 'SUMMARY', 835 'Url': 'HOMEPAGE', 836 'License': 'LICENSE'} 837 foundvalues = {} 838 for fileitem in filelist: 839 linecount = 0 840 with open(fileitem, 'r', errors='surrogateescape') as f: 841 for line in f: 842 for value, varname in valuemap.items(): 843 if line.startswith(value + ':') and not varname in foundvalues: 844 foundvalues[varname] = line.split(':', 1)[1].strip() 845 break 846 if len(foundvalues) == len(valuemap): 847 break 848 # Drop values containing unexpanded RPM macros 849 for k in list(foundvalues.keys()): 850 if '%' in foundvalues[k]: 851 del foundvalues[k] 852 if 'PV' in foundvalues: 853 if not validate_pv(foundvalues['PV']): 854 del foundvalues['PV'] 855 license = foundvalues.pop('LICENSE', None) 856 if license: 857 liccomment = '# NOTE: spec file indicates the license may be "%s"' % license 858 for i, line in enumerate(lines_before): 859 if line.startswith('LICENSE ='): 860 lines_before.insert(i, liccomment) 861 break 862 else: 863 lines_before.append(liccomment) 864 extravalues.update(foundvalues) 865 866def register_recipe_handlers(handlers): 867 # Set priorities with some gaps so that other plugins can insert 868 # their own handlers (so avoid changing these numbers) 869 handlers.append((CmakeRecipeHandler(), 50)) 870 handlers.append((AutotoolsRecipeHandler(), 40)) 871 handlers.append((SconsRecipeHandler(), 30)) 872 handlers.append((QmakeRecipeHandler(), 20)) 873 handlers.append((MakefileRecipeHandler(), 10)) 874 handlers.append((VersionFileRecipeHandler(), -1)) 875 handlers.append((SpecFileRecipeHandler(), -1)) 876