diff options
Diffstat (limited to 'scripts/lib/recipetool/create_buildsys.py')
| -rw-r--r-- | scripts/lib/recipetool/create_buildsys.py | 875 |
1 files changed, 0 insertions, 875 deletions
diff --git a/scripts/lib/recipetool/create_buildsys.py b/scripts/lib/recipetool/create_buildsys.py deleted file mode 100644 index ec9d510e23..0000000000 --- a/scripts/lib/recipetool/create_buildsys.py +++ /dev/null | |||
| @@ -1,875 +0,0 @@ | |||
| 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 | |||
| 8 | import os | ||
| 9 | import re | ||
| 10 | import logging | ||
| 11 | from recipetool.create import RecipeHandler, validate_pv | ||
| 12 | |||
| 13 | logger = logging.getLogger('recipetool') | ||
| 14 | |||
| 15 | tinfoil = None | ||
| 16 | plugins = None | ||
| 17 | |||
| 18 | def plugin_init(pluginlist): | ||
| 19 | # Take a reference to the list so we can use it later | ||
| 20 | global plugins | ||
| 21 | plugins = pluginlist | ||
| 22 | |||
| 23 | def tinfoil_init(instance): | ||
| 24 | global tinfoil | ||
| 25 | tinfoil = instance | ||
| 26 | |||
| 27 | |||
| 28 | class 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 | |||
| 280 | class 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 | |||
| 304 | class 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 | |||
| 319 | class 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 | |||
| 331 | class 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 | |||
| 705 | class 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 | |||
| 734 | class 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 | |||
| 802 | class 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 | |||
| 827 | class 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 | |||
| 866 | def 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)) | ||
