summaryrefslogtreecommitdiffstats
path: root/scripts/lib/recipetool/create_buildsys.py
diff options
context:
space:
mode:
authorPaul Eggleton <paul.eggleton@linux.intel.com>2015-12-22 17:03:02 +1300
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-12-28 09:25:13 +0000
commitdb5f9645ad3ffb4e9be7075d71cb1b13341f5195 (patch)
tree3c4bfcfc698840c310d069f26b57863fde877528 /scripts/lib/recipetool/create_buildsys.py
parent6a7661b8005fadad10bde494131e27406e1e45b8 (diff)
downloadpoky-db5f9645ad3ffb4e9be7075d71cb1b13341f5195.tar.gz
recipetool: create: support extracting name and version from build scripts
Some build systems (notably autotools) support declaring the name and version of the program being built; since we need those for the recipe we can attempt to extract them. It's a little fuzzy as they are often omitted or may not be appropriately formatted for our purposes, but it does work on a reasonable number of software packages to be useful. (From OE-Core rev: 3b3fd33190d89c09e62126eea0e45aa84fe5442e) 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.py282
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
18import re 18import re
19import logging 19import logging
20from recipetool.create import RecipeHandler, read_pkgconfig_provides 20from recipetool.create import RecipeHandler, read_pkgconfig_provides, validate_pv
21 21
22logger = logging.getLogger('recipetool') 22logger = 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
30class CmakeRecipeHandler(RecipeHandler): 31class 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
44class SconsRecipeHandler(RecipeHandler): 66class 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
58class QmakeRecipeHandler(RecipeHandler): 80class 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
69class AutotoolsRecipeHandler(RecipeHandler): 91class 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
242class MakefileRecipeHandler(RecipeHandler): 385class 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
453class 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
478class 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
310def register_recipe_handlers(handlers): 500def 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))