diff options
Diffstat (limited to 'scripts/lib/recipetool/create_buildsys.py')
| -rw-r--r-- | scripts/lib/recipetool/create_buildsys.py | 282 |
1 files changed, 237 insertions, 45 deletions
diff --git a/scripts/lib/recipetool/create_buildsys.py b/scripts/lib/recipetool/create_buildsys.py index 931ef3b33f..0aff59e229 100644 --- a/scripts/lib/recipetool/create_buildsys.py +++ b/scripts/lib/recipetool/create_buildsys.py | |||
| @@ -17,7 +17,7 @@ | |||
| 17 | 17 | ||
| 18 | import re | 18 | import re |
| 19 | import logging | 19 | import logging |
| 20 | from recipetool.create import RecipeHandler, read_pkgconfig_provides | 20 | from recipetool.create import RecipeHandler, read_pkgconfig_provides, validate_pv |
| 21 | 21 | ||
| 22 | logger = logging.getLogger('recipetool') | 22 | logger = logging.getLogger('recipetool') |
| 23 | 23 | ||
| @@ -27,13 +27,17 @@ def tinfoil_init(instance): | |||
| 27 | global tinfoil | 27 | global tinfoil |
| 28 | tinfoil = instance | 28 | tinfoil = instance |
| 29 | 29 | ||
| 30 | |||
| 30 | class CmakeRecipeHandler(RecipeHandler): | 31 | class CmakeRecipeHandler(RecipeHandler): |
| 31 | def process(self, srctree, classes, lines_before, lines_after, handled): | 32 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): |
| 32 | if 'buildsystem' in handled: | 33 | if 'buildsystem' in handled: |
| 33 | return False | 34 | return False |
| 34 | 35 | ||
| 35 | if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']): | 36 | if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']): |
| 36 | classes.append('cmake') | 37 | classes.append('cmake') |
| 38 | values = CmakeRecipeHandler.extract_cmake_deps(lines_before, srctree, extravalues) | ||
| 39 | for var, value in values.iteritems(): | ||
| 40 | lines_before.append('%s = "%s"' % (var, value)) | ||
| 37 | lines_after.append('# Specify any options you want to pass to cmake using EXTRA_OECMAKE:') | 41 | lines_after.append('# Specify any options you want to pass to cmake using EXTRA_OECMAKE:') |
| 38 | lines_after.append('EXTRA_OECMAKE = ""') | 42 | lines_after.append('EXTRA_OECMAKE = ""') |
| 39 | lines_after.append('') | 43 | lines_after.append('') |
| @@ -41,8 +45,26 @@ class CmakeRecipeHandler(RecipeHandler): | |||
| 41 | return True | 45 | return True |
| 42 | return False | 46 | return False |
| 43 | 47 | ||
| 48 | @staticmethod | ||
| 49 | def extract_cmake_deps(outlines, srctree, extravalues, cmakelistsfile=None): | ||
| 50 | values = {} | ||
| 51 | |||
| 52 | if cmakelistsfile: | ||
| 53 | srcfiles = [cmakelistsfile] | ||
| 54 | else: | ||
| 55 | srcfiles = RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']) | ||
| 56 | |||
| 57 | proj_re = re.compile('project\(([^)]*)\)', re.IGNORECASE) | ||
| 58 | with open(srcfiles[0], 'r') as f: | ||
| 59 | for line in f: | ||
| 60 | res = proj_re.match(line.strip()) | ||
| 61 | if res: | ||
| 62 | extravalues['PN'] = res.group(1).split()[0] | ||
| 63 | |||
| 64 | return values | ||
| 65 | |||
| 44 | class SconsRecipeHandler(RecipeHandler): | 66 | class SconsRecipeHandler(RecipeHandler): |
| 45 | def process(self, srctree, classes, lines_before, lines_after, handled): | 67 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): |
| 46 | if 'buildsystem' in handled: | 68 | if 'buildsystem' in handled: |
| 47 | return False | 69 | return False |
| 48 | 70 | ||
| @@ -56,7 +78,7 @@ class SconsRecipeHandler(RecipeHandler): | |||
| 56 | return False | 78 | return False |
| 57 | 79 | ||
| 58 | class QmakeRecipeHandler(RecipeHandler): | 80 | class QmakeRecipeHandler(RecipeHandler): |
| 59 | def process(self, srctree, classes, lines_before, lines_after, handled): | 81 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): |
| 60 | if 'buildsystem' in handled: | 82 | if 'buildsystem' in handled: |
| 61 | return False | 83 | return False |
| 62 | 84 | ||
| @@ -67,14 +89,14 @@ class QmakeRecipeHandler(RecipeHandler): | |||
| 67 | return False | 89 | return False |
| 68 | 90 | ||
| 69 | class AutotoolsRecipeHandler(RecipeHandler): | 91 | class AutotoolsRecipeHandler(RecipeHandler): |
| 70 | def process(self, srctree, classes, lines_before, lines_after, handled): | 92 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): |
| 71 | if 'buildsystem' in handled: | 93 | if 'buildsystem' in handled: |
| 72 | return False | 94 | return False |
| 73 | 95 | ||
| 74 | autoconf = False | 96 | autoconf = False |
| 75 | if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']): | 97 | if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']): |
| 76 | autoconf = True | 98 | autoconf = True |
| 77 | values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree) | 99 | values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, extravalues) |
| 78 | classes.extend(values.pop('inherit', '').split()) | 100 | classes.extend(values.pop('inherit', '').split()) |
| 79 | for var, value in values.iteritems(): | 101 | for var, value in values.iteritems(): |
| 80 | lines_before.append('%s = "%s"' % (var, value)) | 102 | lines_before.append('%s = "%s"' % (var, value)) |
| @@ -88,6 +110,22 @@ class AutotoolsRecipeHandler(RecipeHandler): | |||
| 88 | autoconf = True | 110 | autoconf = True |
| 89 | break | 111 | break |
| 90 | 112 | ||
| 113 | if autoconf and not ('PV' in extravalues and 'PN' in extravalues): | ||
| 114 | # Last resort | ||
| 115 | conffile = RecipeHandler.checkfiles(srctree, ['configure']) | ||
| 116 | if conffile: | ||
| 117 | with open(conffile[0], 'r') as f: | ||
| 118 | for line in f: | ||
| 119 | line = line.strip() | ||
| 120 | if line.startswith('VERSION=') or line.startswith('PACKAGE_VERSION='): | ||
| 121 | pv = line.split('=')[1].strip('"\'') | ||
| 122 | if pv and not 'PV' in extravalues and validate_pv(pv): | ||
| 123 | extravalues['PV'] = pv | ||
| 124 | elif line.startswith('PACKAGE_NAME=') or line.startswith('PACKAGE='): | ||
| 125 | pn = line.split('=')[1].strip('"\'') | ||
| 126 | if pn and not 'PN' in extravalues: | ||
| 127 | extravalues['PN'] = pn | ||
| 128 | |||
| 91 | if autoconf: | 129 | if autoconf: |
| 92 | lines_before.append('# NOTE: if this software is not capable of being built in a separate build directory') | 130 | lines_before.append('# NOTE: if this software is not capable of being built in a separate build directory') |
| 93 | lines_before.append('# from the source, you should replace autotools with autotools-brokensep in the') | 131 | lines_before.append('# from the source, you should replace autotools with autotools-brokensep in the') |
| @@ -102,7 +140,7 @@ class AutotoolsRecipeHandler(RecipeHandler): | |||
| 102 | return False | 140 | return False |
| 103 | 141 | ||
| 104 | @staticmethod | 142 | @staticmethod |
| 105 | def extract_autotools_deps(outlines, srctree, acfile=None): | 143 | def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None): |
| 106 | import shlex | 144 | import shlex |
| 107 | import oe.package | 145 | import oe.package |
| 108 | 146 | ||
| @@ -122,6 +160,9 @@ class AutotoolsRecipeHandler(RecipeHandler): | |||
| 122 | lib_re = re.compile('AC_CHECK_LIB\(\[?([a-zA-Z0-9]*)\]?, .*') | 160 | lib_re = re.compile('AC_CHECK_LIB\(\[?([a-zA-Z0-9]*)\]?, .*') |
| 123 | progs_re = re.compile('_PROGS?\(\[?[a-zA-Z0-9]*\]?, \[?([^,\]]*)\]?[),].*') | 161 | progs_re = re.compile('_PROGS?\(\[?[a-zA-Z0-9]*\]?, \[?([^,\]]*)\]?[),].*') |
| 124 | dep_re = re.compile('([^ ><=]+)( [<>=]+ [^ ><=]+)?') | 162 | dep_re = re.compile('([^ ><=]+)( [<>=]+ [^ ><=]+)?') |
| 163 | ac_init_re = re.compile('AC_INIT\(([^,]+), *([^,]+)[,)].*') | ||
| 164 | am_init_re = re.compile('AM_INIT_AUTOMAKE\(([^,]+), *([^,]+)[,)].*') | ||
| 165 | define_re = re.compile(' *(m4_)?define\(([^,]+), *([^,]+)\)') | ||
| 125 | 166 | ||
| 126 | # Build up lib library->package mapping | 167 | # Build up lib library->package mapping |
| 127 | shlib_providers = oe.package.read_shlib_providers(tinfoil.config_data) | 168 | shlib_providers = oe.package.read_shlib_providers(tinfoil.config_data) |
| @@ -157,55 +198,157 @@ class AutotoolsRecipeHandler(RecipeHandler): | |||
| 157 | else: | 198 | else: |
| 158 | raise | 199 | raise |
| 159 | 200 | ||
| 201 | defines = {} | ||
| 202 | def subst_defines(value): | ||
| 203 | newvalue = value | ||
| 204 | for define, defval in defines.iteritems(): | ||
| 205 | newvalue = newvalue.replace(define, defval) | ||
| 206 | if newvalue != value: | ||
| 207 | return subst_defines(newvalue) | ||
| 208 | return value | ||
| 209 | |||
| 210 | def process_value(value): | ||
| 211 | value = value.replace('[', '').replace(']', '') | ||
| 212 | if value.startswith('m4_esyscmd(') or value.startswith('m4_esyscmd_s('): | ||
| 213 | cmd = subst_defines(value[value.index('(')+1:-1]) | ||
| 214 | try: | ||
| 215 | if '|' in cmd: | ||
| 216 | cmd = 'set -o pipefail; ' + cmd | ||
| 217 | stdout, _ = bb.process.run(cmd, cwd=srctree, shell=True) | ||
| 218 | ret = stdout.rstrip() | ||
| 219 | except bb.process.ExecutionError as e: | ||
| 220 | ret = '' | ||
| 221 | elif value.startswith('m4_'): | ||
| 222 | return None | ||
| 223 | ret = subst_defines(value) | ||
| 224 | if ret: | ||
| 225 | ret = ret.strip('"\'') | ||
| 226 | return ret | ||
| 227 | |||
| 160 | # Since a configure.ac file is essentially a program, this is only ever going to be | 228 | # Since a configure.ac file is essentially a program, this is only ever going to be |
| 161 | # a hack unfortunately; but it ought to be enough of an approximation | 229 | # a hack unfortunately; but it ought to be enough of an approximation |
| 162 | if acfile: | 230 | if acfile: |
| 163 | srcfiles = [acfile] | 231 | srcfiles = [acfile] |
| 164 | else: | 232 | else: |
| 165 | srcfiles = RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']) | 233 | srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in']) |
| 234 | |||
| 166 | pcdeps = [] | 235 | pcdeps = [] |
| 167 | deps = [] | 236 | deps = [] |
| 168 | unmapped = [] | 237 | unmapped = [] |
| 169 | unmappedlibs = [] | 238 | unmappedlibs = [] |
| 170 | with open(srcfiles[0], 'r') as f: | 239 | |
| 171 | for line in f: | 240 | def process_macro(keyword, value): |
| 172 | if 'PKG_CHECK_MODULES' in line: | 241 | if keyword == 'PKG_CHECK_MODULES': |
| 173 | res = pkg_re.search(line) | 242 | res = pkg_re.search(value) |
| 174 | if res: | 243 | if res: |
| 175 | res = dep_re.findall(res.group(1)) | 244 | res = dep_re.findall(res.group(1)) |
| 176 | if res: | ||
| 177 | pcdeps.extend([x[0] for x in res]) | ||
| 178 | inherits.append('pkgconfig') | ||
| 179 | if line.lstrip().startswith('AM_GNU_GETTEXT'): | ||
| 180 | inherits.append('gettext') | ||
| 181 | elif 'AC_CHECK_PROG' in line or 'AC_PATH_PROG' in line: | ||
| 182 | res = progs_re.search(line) | ||
| 183 | if res: | 245 | if res: |
| 184 | for prog in shlex.split(res.group(1)): | 246 | pcdeps.extend([x[0] for x in res]) |
| 185 | prog = prog.split()[0] | 247 | inherits.append('pkgconfig') |
| 186 | progclass = progclassmap.get(prog, None) | 248 | elif keyword == 'AM_GNU_GETTEXT': |
| 187 | if progclass: | 249 | inherits.append('gettext') |
| 188 | inherits.append(progclass) | 250 | elif keyword == 'AC_CHECK_PROG' or keyword == 'AC_PATH_PROG': |
| 251 | res = progs_re.search(value) | ||
| 252 | if res: | ||
| 253 | for prog in shlex.split(res.group(1)): | ||
| 254 | prog = prog.split()[0] | ||
| 255 | progclass = progclassmap.get(prog, None) | ||
| 256 | if progclass: | ||
| 257 | inherits.append(progclass) | ||
| 258 | else: | ||
| 259 | progdep = progmap.get(prog, None) | ||
| 260 | if progdep: | ||
| 261 | deps.append(progdep) | ||
| 189 | else: | 262 | else: |
| 190 | progdep = progmap.get(prog, None) | 263 | if not prog.startswith('$'): |
| 191 | if progdep: | 264 | unmapped.append(prog) |
| 192 | deps.append(progdep) | 265 | elif keyword == 'AC_CHECK_LIB': |
| 193 | else: | 266 | res = lib_re.search(value) |
| 194 | if not prog.startswith('$'): | 267 | if res: |
| 195 | unmapped.append(prog) | 268 | lib = res.group(1) |
| 196 | elif 'AC_CHECK_LIB' in line: | 269 | libdep = recipelibmap.get(lib, None) |
| 197 | res = lib_re.search(line) | 270 | if libdep: |
| 271 | deps.append(libdep) | ||
| 272 | else: | ||
| 273 | if libdep is None: | ||
| 274 | if not lib.startswith('$'): | ||
| 275 | unmappedlibs.append(lib) | ||
| 276 | elif keyword == 'AC_PATH_X': | ||
| 277 | deps.append('libx11') | ||
| 278 | elif keyword == 'AC_INIT': | ||
| 279 | if extravalues is not None: | ||
| 280 | res = ac_init_re.match(value) | ||
| 198 | if res: | 281 | if res: |
| 199 | lib = res.group(1) | 282 | extravalues['PN'] = process_value(res.group(1)) |
| 200 | libdep = recipelibmap.get(lib, None) | 283 | pv = process_value(res.group(2)) |
| 201 | if libdep: | 284 | if validate_pv(pv): |
| 202 | deps.append(libdep) | 285 | extravalues['PV'] = pv |
| 203 | else: | 286 | elif keyword == 'AM_INIT_AUTOMAKE': |
| 204 | if libdep is None: | 287 | if extravalues is not None: |
| 205 | if not lib.startswith('$'): | 288 | if 'PN' not in extravalues: |
| 206 | unmappedlibs.append(lib) | 289 | res = am_init_re.match(value) |
| 207 | elif 'AC_PATH_X' in line: | 290 | if res: |
| 208 | deps.append('libx11') | 291 | if res.group(1) != 'AC_PACKAGE_NAME': |
| 292 | extravalues['PN'] = process_value(res.group(1)) | ||
| 293 | pv = process_value(res.group(2)) | ||
| 294 | if validate_pv(pv): | ||
| 295 | extravalues['PV'] = pv | ||
| 296 | elif keyword == 'define(': | ||
| 297 | res = define_re.match(value) | ||
| 298 | if res: | ||
| 299 | key = res.group(2).strip('[]') | ||
| 300 | value = process_value(res.group(3)) | ||
| 301 | if value is not None: | ||
| 302 | defines[key] = value | ||
| 303 | |||
| 304 | keywords = ['PKG_CHECK_MODULES', | ||
| 305 | 'AM_GNU_GETTEXT', | ||
| 306 | 'AC_CHECK_PROG', | ||
| 307 | 'AC_PATH_PROG', | ||
| 308 | 'AC_CHECK_LIB', | ||
| 309 | 'AC_PATH_X', | ||
| 310 | 'AC_INIT', | ||
| 311 | 'AM_INIT_AUTOMAKE', | ||
| 312 | 'define(', | ||
| 313 | ] | ||
| 314 | for srcfile in srcfiles: | ||
| 315 | nesting = 0 | ||
| 316 | in_keyword = '' | ||
| 317 | partial = '' | ||
| 318 | with open(srcfile, 'r') as f: | ||
| 319 | for line in f: | ||
| 320 | if in_keyword: | ||
| 321 | partial += ' ' + line.strip() | ||
| 322 | if partial.endswith('\\'): | ||
| 323 | partial = partial[:-1] | ||
| 324 | nesting = nesting + line.count('(') - line.count(')') | ||
| 325 | if nesting == 0: | ||
| 326 | process_macro(in_keyword, partial) | ||
| 327 | partial = '' | ||
| 328 | in_keyword = '' | ||
| 329 | else: | ||
| 330 | for keyword in keywords: | ||
| 331 | if keyword in line: | ||
| 332 | nesting = line.count('(') - line.count(')') | ||
| 333 | if nesting > 0: | ||
| 334 | partial = line.strip() | ||
| 335 | if partial.endswith('\\'): | ||
| 336 | partial = partial[:-1] | ||
| 337 | in_keyword = keyword | ||
| 338 | else: | ||
| 339 | process_macro(keyword, line.strip()) | ||
| 340 | break | ||
| 341 | |||
| 342 | if in_keyword: | ||
| 343 | process_macro(in_keyword, partial) | ||
| 344 | |||
| 345 | if extravalues: | ||
| 346 | for k,v in extravalues.items(): | ||
| 347 | if v: | ||
| 348 | if v.startswith('$') or v.startswith('@') or v.startswith('%'): | ||
| 349 | del extravalues[k] | ||
| 350 | else: | ||
| 351 | extravalues[k] = v.strip('"\'').rstrip('()') | ||
| 209 | 352 | ||
| 210 | if unmapped: | 353 | if unmapped: |
| 211 | outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(unmapped)) | 354 | outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(unmapped)) |
| @@ -240,7 +383,7 @@ class AutotoolsRecipeHandler(RecipeHandler): | |||
| 240 | 383 | ||
| 241 | 384 | ||
| 242 | class MakefileRecipeHandler(RecipeHandler): | 385 | class MakefileRecipeHandler(RecipeHandler): |
| 243 | def process(self, srctree, classes, lines_before, lines_after, handled): | 386 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): |
| 244 | if 'buildsystem' in handled: | 387 | if 'buildsystem' in handled: |
| 245 | return False | 388 | return False |
| 246 | 389 | ||
| @@ -307,6 +450,53 @@ class MakefileRecipeHandler(RecipeHandler): | |||
| 307 | self.genfunction(lines_after, 'do_install', ['# Specify install commands here']) | 450 | self.genfunction(lines_after, 'do_install', ['# Specify install commands here']) |
| 308 | 451 | ||
| 309 | 452 | ||
| 453 | class VersionFileRecipeHandler(RecipeHandler): | ||
| 454 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): | ||
| 455 | if 'PV' not in extravalues: | ||
| 456 | # Look for a VERSION or version file containing a single line consisting | ||
| 457 | # only of a version number | ||
| 458 | filelist = RecipeHandler.checkfiles(srctree, ['VERSION', 'version']) | ||
| 459 | version = None | ||
| 460 | for fileitem in filelist: | ||
| 461 | linecount = 0 | ||
| 462 | with open(fileitem, 'r') as f: | ||
| 463 | for line in f: | ||
| 464 | line = line.rstrip().strip('"\'') | ||
| 465 | linecount += 1 | ||
| 466 | if line: | ||
| 467 | if linecount > 1: | ||
| 468 | version = None | ||
| 469 | break | ||
| 470 | else: | ||
| 471 | if validate_pv(line): | ||
| 472 | version = line | ||
| 473 | if version: | ||
| 474 | extravalues['PV'] = version | ||
| 475 | break | ||
| 476 | |||
| 477 | |||
| 478 | class SpecFileRecipeHandler(RecipeHandler): | ||
| 479 | def process(self, srctree, classes, lines_before, lines_after, handled, extravalues): | ||
| 480 | if 'PV' in extravalues and 'PN' in extravalues: | ||
| 481 | return | ||
| 482 | filelist = RecipeHandler.checkfiles(srctree, ['*.spec'], recursive=True) | ||
| 483 | pn = None | ||
| 484 | pv = None | ||
| 485 | for fileitem in filelist: | ||
| 486 | linecount = 0 | ||
| 487 | with open(fileitem, 'r') as f: | ||
| 488 | for line in f: | ||
| 489 | if line.startswith('Name:') and not pn: | ||
| 490 | pn = line.split(':')[1].strip() | ||
| 491 | if line.startswith('Version:') and not pv: | ||
| 492 | pv = line.split(':')[1].strip() | ||
| 493 | if pv or pn: | ||
| 494 | if pv and not 'PV' in extravalues and validate_pv(pv): | ||
| 495 | extravalues['PV'] = pv | ||
| 496 | if pn and not 'PN' in extravalues: | ||
| 497 | extravalues['PN'] = pn | ||
| 498 | break | ||
| 499 | |||
| 310 | def register_recipe_handlers(handlers): | 500 | def register_recipe_handlers(handlers): |
| 311 | # These are in a specific order so that the right one is detected first | 501 | # These are in a specific order so that the right one is detected first |
| 312 | handlers.append(CmakeRecipeHandler()) | 502 | handlers.append(CmakeRecipeHandler()) |
| @@ -314,3 +504,5 @@ def register_recipe_handlers(handlers): | |||
| 314 | handlers.append(SconsRecipeHandler()) | 504 | handlers.append(SconsRecipeHandler()) |
| 315 | handlers.append(QmakeRecipeHandler()) | 505 | handlers.append(QmakeRecipeHandler()) |
| 316 | handlers.append(MakefileRecipeHandler()) | 506 | handlers.append(MakefileRecipeHandler()) |
| 507 | handlers.append((VersionFileRecipeHandler(), -1)) | ||
| 508 | handlers.append((SpecFileRecipeHandler(), -1)) | ||
