diff options
| author | Paul Eggleton <paul.eggleton@linux.intel.com> | 2014-12-19 11:41:53 +0000 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2014-12-23 10:18:15 +0000 |
| commit | 5638ca2b9483806d991d93071b259769a5f7ec48 (patch) | |
| tree | a40d3d01188b8fd800252ca6b29e11690a31e619 /scripts/lib/recipetool/create_buildsys.py | |
| parent | f176b0c64f53e27c2a3e93e879405ebea007f9f7 (diff) | |
| download | poky-5638ca2b9483806d991d93071b259769a5f7ec48.tar.gz | |
scripts/recipetool: Add a recipe auto-creation script
Add a more maintainable and flexible script for creating at least the
skeleton of a recipe based on an examination of the source tree.
Commands can be added and the creation process can be extended through
plugins.
[YOCTO #6406]
(From OE-Core rev: fa07ada1cd0750f9aa6bcc31f8236205edf6b4ed)
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'scripts/lib/recipetool/create_buildsys.py')
| -rw-r--r-- | scripts/lib/recipetool/create_buildsys.py | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/scripts/lib/recipetool/create_buildsys.py b/scripts/lib/recipetool/create_buildsys.py new file mode 100644 index 0000000000..6c9e0efa2a --- /dev/null +++ b/scripts/lib/recipetool/create_buildsys.py | |||
| @@ -0,0 +1,319 @@ | |||
| 1 | # Recipe creation tool - create command build system handlers | ||
| 2 | # | ||
| 3 | # Copyright (C) 2014 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 | |||
| 18 | import re | ||
| 19 | import logging | ||
| 20 | from recipetool.create import RecipeHandler, read_pkgconfig_provides | ||
| 21 | |||
| 22 | logger = logging.getLogger('recipetool') | ||
| 23 | |||
| 24 | tinfoil = None | ||
| 25 | |||
| 26 | def tinfoil_init(instance): | ||
| 27 | global tinfoil | ||
| 28 | tinfoil = instance | ||
| 29 | |||
| 30 | class CmakeRecipeHandler(RecipeHandler): | ||
| 31 | def process(self, srctree, classes, lines_before, lines_after, handled): | ||
| 32 | if 'buildsystem' in handled: | ||
| 33 | return False | ||
| 34 | |||
| 35 | if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']): | ||
| 36 | classes.append('cmake') | ||
| 37 | lines_after.append('# Specify any options you want to pass to cmake using EXTRA_OECMAKE:') | ||
| 38 | lines_after.append('EXTRA_OECMAKE = ""') | ||
| 39 | lines_after.append('') | ||
| 40 | handled.append('buildsystem') | ||
| 41 | return True | ||
| 42 | return False | ||
| 43 | |||
| 44 | class SconsRecipeHandler(RecipeHandler): | ||
| 45 | def process(self, srctree, classes, lines_before, lines_after, handled): | ||
| 46 | if 'buildsystem' in handled: | ||
| 47 | return False | ||
| 48 | |||
| 49 | if RecipeHandler.checkfiles(srctree, ['SConstruct', 'Sconstruct', 'sconstruct']): | ||
| 50 | classes.append('scons') | ||
| 51 | lines_after.append('# Specify any options you want to pass to scons using EXTRA_OESCONS:') | ||
| 52 | lines_after.append('EXTRA_OESCONS = ""') | ||
| 53 | lines_after.append('') | ||
| 54 | handled.append('buildsystem') | ||
| 55 | return True | ||
| 56 | return False | ||
| 57 | |||
| 58 | class QmakeRecipeHandler(RecipeHandler): | ||
| 59 | def process(self, srctree, classes, lines_before, lines_after, handled): | ||
| 60 | if 'buildsystem' in handled: | ||
| 61 | return False | ||
| 62 | |||
| 63 | if RecipeHandler.checkfiles(srctree, ['*.pro']): | ||
| 64 | classes.append('qmake2') | ||
| 65 | handled.append('buildsystem') | ||
| 66 | return True | ||
| 67 | return False | ||
| 68 | |||
| 69 | class AutotoolsRecipeHandler(RecipeHandler): | ||
| 70 | def process(self, srctree, classes, lines_before, lines_after, handled): | ||
| 71 | if 'buildsystem' in handled: | ||
| 72 | return False | ||
| 73 | |||
| 74 | autoconf = False | ||
| 75 | if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']): | ||
| 76 | autoconf = True | ||
| 77 | values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree) | ||
| 78 | classes.extend(values.pop('inherit', '').split()) | ||
| 79 | for var, value in values.iteritems(): | ||
| 80 | lines_before.append('%s = "%s"' % (var, value)) | ||
| 81 | else: | ||
| 82 | conffile = RecipeHandler.checkfiles(srctree, ['configure']) | ||
| 83 | if conffile: | ||
| 84 | # Check if this is just a pre-generated autoconf configure script | ||
| 85 | with open(conffile[0], 'r') as f: | ||
| 86 | for i in range(1, 10): | ||
| 87 | if 'Generated by GNU Autoconf' in f.readline(): | ||
| 88 | autoconf = True | ||
| 89 | break | ||
| 90 | |||
| 91 | if autoconf: | ||
| 92 | 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') | ||
| 94 | lines_before.append('# inherit line') | ||
| 95 | classes.append('autotools') | ||
| 96 | lines_after.append('# Specify any options you want to pass to the configure script using EXTRA_OECONF:') | ||
| 97 | lines_after.append('EXTRA_OECONF = ""') | ||
| 98 | lines_after.append('') | ||
| 99 | handled.append('buildsystem') | ||
| 100 | return True | ||
| 101 | |||
| 102 | return False | ||
| 103 | |||
| 104 | @staticmethod | ||
| 105 | def extract_autotools_deps(outlines, srctree, acfile=None): | ||
| 106 | import shlex | ||
| 107 | import oe.package | ||
| 108 | |||
| 109 | values = {} | ||
| 110 | inherits = [] | ||
| 111 | |||
| 112 | # FIXME this mapping is very thin | ||
| 113 | progmap = {'flex': 'flex-native', | ||
| 114 | 'bison': 'bison-native', | ||
| 115 | 'm4': 'm4-native'} | ||
| 116 | progclassmap = {'gconftool-2': 'gconf', | ||
| 117 | 'pkg-config': 'pkgconfig'} | ||
| 118 | |||
| 119 | ignoredeps = ['gcc-runtime', 'glibc', 'uclibc'] | ||
| 120 | |||
| 121 | pkg_re = re.compile('PKG_CHECK_MODULES\(\[?[a-zA-Z0-9]*\]?, \[?([^,\]]*)[),].*') | ||
| 122 | lib_re = re.compile('AC_CHECK_LIB\(\[?([a-zA-Z0-9]*)\]?, .*') | ||
| 123 | progs_re = re.compile('_PROGS?\(\[?[a-zA-Z0-9]*\]?, \[?([^,\]]*)\]?[),].*') | ||
| 124 | dep_re = re.compile('([^ ><=]+)( [<>=]+ [^ ><=]+)?') | ||
| 125 | |||
| 126 | # Build up lib library->package mapping | ||
| 127 | shlib_providers = oe.package.read_shlib_providers(tinfoil.config_data) | ||
| 128 | libdir = tinfoil.config_data.getVar('libdir', True) | ||
| 129 | base_libdir = tinfoil.config_data.getVar('base_libdir', True) | ||
| 130 | libpaths = list(set([base_libdir, libdir])) | ||
| 131 | libname_re = re.compile('^lib(.+)\.so.*$') | ||
| 132 | pkglibmap = {} | ||
| 133 | for lib, item in shlib_providers.iteritems(): | ||
| 134 | for path, pkg in item.iteritems(): | ||
| 135 | if path in libpaths: | ||
| 136 | res = libname_re.match(lib) | ||
| 137 | if res: | ||
| 138 | libname = res.group(1) | ||
| 139 | if not libname in pkglibmap: | ||
| 140 | pkglibmap[libname] = pkg[0] | ||
| 141 | else: | ||
| 142 | logger.debug('unable to extract library name from %s' % lib) | ||
| 143 | |||
| 144 | # Now turn it into a library->recipe mapping | ||
| 145 | recipelibmap = {} | ||
| 146 | pkgdata_dir = tinfoil.config_data.getVar('PKGDATA_DIR', True) | ||
| 147 | for libname, pkg in pkglibmap.iteritems(): | ||
| 148 | try: | ||
| 149 | with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f: | ||
| 150 | for line in f: | ||
| 151 | if line.startswith('PN:'): | ||
| 152 | recipelibmap[libname] = line.split(':', 1)[-1].strip() | ||
| 153 | break | ||
| 154 | except IOError as ioe: | ||
| 155 | if ioe.errno == 2: | ||
| 156 | logger.warn('unable to find a pkgdata file for package %s' % pkg) | ||
| 157 | else: | ||
| 158 | raise | ||
| 159 | |||
| 160 | # 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 | ||
| 162 | if acfile: | ||
| 163 | srcfiles = [acfile] | ||
| 164 | else: | ||
| 165 | srcfiles = RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']) | ||
| 166 | pcdeps = [] | ||
| 167 | deps = [] | ||
| 168 | unmapped = [] | ||
| 169 | unmappedlibs = [] | ||
| 170 | with open(srcfiles[0], 'r') as f: | ||
| 171 | for line in f: | ||
| 172 | if 'PKG_CHECK_MODULES' in line: | ||
| 173 | res = pkg_re.search(line) | ||
| 174 | if res: | ||
| 175 | 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: | ||
| 184 | for prog in shlex.split(res.group(1)): | ||
| 185 | prog = prog.split()[0] | ||
| 186 | progclass = progclassmap.get(prog, None) | ||
| 187 | if progclass: | ||
| 188 | inherits.append(progclass) | ||
| 189 | else: | ||
| 190 | progdep = progmap.get(prog, None) | ||
| 191 | if progdep: | ||
| 192 | deps.append(progdep) | ||
| 193 | else: | ||
| 194 | if not prog.startswith('$'): | ||
| 195 | unmapped.append(prog) | ||
| 196 | elif 'AC_CHECK_LIB' in line: | ||
| 197 | res = lib_re.search(line) | ||
| 198 | if res: | ||
| 199 | lib = res.group(1) | ||
| 200 | libdep = recipelibmap.get(lib, None) | ||
| 201 | if libdep: | ||
| 202 | deps.append(libdep) | ||
| 203 | else: | ||
| 204 | if libdep is None: | ||
| 205 | if not lib.startswith('$'): | ||
| 206 | unmappedlibs.append(lib) | ||
| 207 | elif 'AC_PATH_X' in line: | ||
| 208 | deps.append('libx11') | ||
| 209 | |||
| 210 | if unmapped: | ||
| 211 | outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(unmapped)) | ||
| 212 | |||
| 213 | if unmappedlibs: | ||
| 214 | outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(unmappedlibs)) | ||
| 215 | outlines.append('# (this is based on recipes that have previously been built and packaged)') | ||
| 216 | |||
| 217 | recipemap = read_pkgconfig_provides(tinfoil.config_data) | ||
| 218 | unmapped = [] | ||
| 219 | for pcdep in pcdeps: | ||
| 220 | recipe = recipemap.get(pcdep, None) | ||
| 221 | if recipe: | ||
| 222 | deps.append(recipe) | ||
| 223 | else: | ||
| 224 | if not pcdep.startswith('$'): | ||
| 225 | unmapped.append(pcdep) | ||
| 226 | |||
| 227 | deps = set(deps).difference(set(ignoredeps)) | ||
| 228 | |||
| 229 | if unmapped: | ||
| 230 | outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmapped)) | ||
| 231 | outlines.append('# (this is based on recipes that have previously been built and packaged)') | ||
| 232 | |||
| 233 | if deps: | ||
| 234 | values['DEPENDS'] = ' '.join(deps) | ||
| 235 | |||
| 236 | if inherits: | ||
| 237 | values['inherit'] = ' '.join(list(set(inherits))) | ||
| 238 | |||
| 239 | return values | ||
| 240 | |||
| 241 | |||
| 242 | class MakefileRecipeHandler(RecipeHandler): | ||
| 243 | def process(self, srctree, classes, lines_before, lines_after, handled): | ||
| 244 | if 'buildsystem' in handled: | ||
| 245 | return False | ||
| 246 | |||
| 247 | makefile = RecipeHandler.checkfiles(srctree, ['Makefile']) | ||
| 248 | if makefile: | ||
| 249 | lines_after.append('# NOTE: this is a Makefile-only piece of software, so we cannot generate much of the') | ||
| 250 | lines_after.append('# recipe automatically - you will need to examine the Makefile yourself and ensure') | ||
| 251 | lines_after.append('# that the appropriate arguments are passed in.') | ||
| 252 | lines_after.append('') | ||
| 253 | |||
| 254 | scanfile = os.path.join(srctree, 'configure.scan') | ||
| 255 | skipscan = False | ||
| 256 | try: | ||
| 257 | stdout, stderr = bb.process.run('autoscan', cwd=srctree, shell=True) | ||
| 258 | except bb.process.ExecutionError as e: | ||
| 259 | skipscan = True | ||
| 260 | if scanfile and os.path.exists(scanfile): | ||
| 261 | values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, acfile=scanfile) | ||
| 262 | classes.extend(values.pop('inherit', '').split()) | ||
| 263 | for var, value in values.iteritems(): | ||
| 264 | if var == 'DEPENDS': | ||
| 265 | lines_before.append('# NOTE: some of these dependencies may be optional, check the Makefile and/or upstream documentation') | ||
| 266 | lines_before.append('%s = "%s"' % (var, value)) | ||
| 267 | lines_before.append('') | ||
| 268 | for f in ['configure.scan', 'autoscan.log']: | ||
| 269 | fp = os.path.join(srctree, f) | ||
| 270 | if os.path.exists(fp): | ||
| 271 | os.remove(fp) | ||
| 272 | |||
| 273 | self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here']) | ||
| 274 | |||
| 275 | func = [] | ||
| 276 | func.append('# You will almost certainly need to add additional arguments here') | ||
| 277 | func.append('oe_runmake') | ||
| 278 | self.genfunction(lines_after, 'do_compile', func) | ||
| 279 | |||
| 280 | installtarget = True | ||
| 281 | try: | ||
| 282 | stdout, stderr = bb.process.run('make -qn install', cwd=srctree, shell=True) | ||
| 283 | except bb.process.ExecutionError as e: | ||
| 284 | if e.exitcode != 1: | ||
| 285 | installtarget = False | ||
| 286 | func = [] | ||
| 287 | if installtarget: | ||
| 288 | func.append('# This is a guess; additional arguments may be required') | ||
| 289 | makeargs = '' | ||
| 290 | with open(makefile[0], 'r') as f: | ||
| 291 | for i in range(1, 100): | ||
| 292 | if 'DESTDIR' in f.readline(): | ||
| 293 | makeargs += " 'DESTDIR=${D}'" | ||
| 294 | break | ||
| 295 | func.append('oe_runmake install%s' % makeargs) | ||
| 296 | else: | ||
| 297 | func.append('# NOTE: unable to determine what to put here - there is a Makefile but no') | ||
| 298 | func.append('# target named "install", so you will need to define this yourself') | ||
| 299 | self.genfunction(lines_after, 'do_install', func) | ||
| 300 | |||
| 301 | handled.append('buildsystem') | ||
| 302 | else: | ||
| 303 | lines_after.append('# NOTE: no Makefile found, unable to determine what needs to be done') | ||
| 304 | lines_after.append('') | ||
| 305 | self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here']) | ||
| 306 | self.genfunction(lines_after, 'do_compile', ['# Specify compilation commands here']) | ||
| 307 | self.genfunction(lines_after, 'do_install', ['# Specify install commands here']) | ||
| 308 | |||
| 309 | |||
| 310 | def plugin_init(pluginlist): | ||
| 311 | pass | ||
| 312 | |||
| 313 | def register_recipe_handlers(handlers): | ||
| 314 | # These are in a specific order so that the right one is detected first | ||
| 315 | handlers.append(CmakeRecipeHandler()) | ||
| 316 | handlers.append(AutotoolsRecipeHandler()) | ||
| 317 | handlers.append(SconsRecipeHandler()) | ||
| 318 | handlers.append(QmakeRecipeHandler()) | ||
| 319 | handlers.append(MakefileRecipeHandler()) | ||
