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()) | ||