xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/recipetool/create_buildsys.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Recipe creation tool - create command build system handlers
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# Copyright (C) 2014-2016 Intel Corporation
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun
8*4882a593Smuzhiyunimport re
9*4882a593Smuzhiyunimport logging
10*4882a593Smuzhiyunimport glob
11*4882a593Smuzhiyunfrom recipetool.create import RecipeHandler, validate_pv
12*4882a593Smuzhiyun
13*4882a593Smuzhiyunlogger = logging.getLogger('recipetool')
14*4882a593Smuzhiyun
15*4882a593Smuzhiyuntinfoil = None
16*4882a593Smuzhiyunplugins = None
17*4882a593Smuzhiyun
18*4882a593Smuzhiyundef plugin_init(pluginlist):
19*4882a593Smuzhiyun    # Take a reference to the list so we can use it later
20*4882a593Smuzhiyun    global plugins
21*4882a593Smuzhiyun    plugins = pluginlist
22*4882a593Smuzhiyun
23*4882a593Smuzhiyundef tinfoil_init(instance):
24*4882a593Smuzhiyun    global tinfoil
25*4882a593Smuzhiyun    tinfoil = instance
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun
28*4882a593Smuzhiyunclass CmakeRecipeHandler(RecipeHandler):
29*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
30*4882a593Smuzhiyun        if 'buildsystem' in handled:
31*4882a593Smuzhiyun            return False
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun        if RecipeHandler.checkfiles(srctree, ['CMakeLists.txt']):
34*4882a593Smuzhiyun            classes.append('cmake')
35*4882a593Smuzhiyun            values = CmakeRecipeHandler.extract_cmake_deps(lines_before, srctree, extravalues)
36*4882a593Smuzhiyun            classes.extend(values.pop('inherit', '').split())
37*4882a593Smuzhiyun            for var, value in values.items():
38*4882a593Smuzhiyun                lines_before.append('%s = "%s"' % (var, value))
39*4882a593Smuzhiyun            lines_after.append('# Specify any options you want to pass to cmake using EXTRA_OECMAKE:')
40*4882a593Smuzhiyun            lines_after.append('EXTRA_OECMAKE = ""')
41*4882a593Smuzhiyun            lines_after.append('')
42*4882a593Smuzhiyun            handled.append('buildsystem')
43*4882a593Smuzhiyun            return True
44*4882a593Smuzhiyun        return False
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun    @staticmethod
47*4882a593Smuzhiyun    def extract_cmake_deps(outlines, srctree, extravalues, cmakelistsfile=None):
48*4882a593Smuzhiyun        # Find all plugins that want to register handlers
49*4882a593Smuzhiyun        logger.debug('Loading cmake handlers')
50*4882a593Smuzhiyun        handlers = []
51*4882a593Smuzhiyun        for plugin in plugins:
52*4882a593Smuzhiyun            if hasattr(plugin, 'register_cmake_handlers'):
53*4882a593Smuzhiyun                plugin.register_cmake_handlers(handlers)
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun        values = {}
56*4882a593Smuzhiyun        inherits = []
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun        if cmakelistsfile:
59*4882a593Smuzhiyun            srcfiles = [cmakelistsfile]
60*4882a593Smuzhiyun        else:
61*4882a593Smuzhiyun            srcfiles = RecipeHandler.checkfiles(srctree, ['CMakeLists.txt'])
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun        # Note that some of these are non-standard, but probably better to
64*4882a593Smuzhiyun        # be able to map them anyway if we see them
65*4882a593Smuzhiyun        cmake_pkgmap = {'alsa': 'alsa-lib',
66*4882a593Smuzhiyun                        'aspell': 'aspell',
67*4882a593Smuzhiyun                        'atk': 'atk',
68*4882a593Smuzhiyun                        'bison': 'bison-native',
69*4882a593Smuzhiyun                        'boost': 'boost',
70*4882a593Smuzhiyun                        'bzip2': 'bzip2',
71*4882a593Smuzhiyun                        'cairo': 'cairo',
72*4882a593Smuzhiyun                        'cups': 'cups',
73*4882a593Smuzhiyun                        'curl': 'curl',
74*4882a593Smuzhiyun                        'curses': 'ncurses',
75*4882a593Smuzhiyun                        'cvs': 'cvs',
76*4882a593Smuzhiyun                        'drm': 'libdrm',
77*4882a593Smuzhiyun                        'dbus': 'dbus',
78*4882a593Smuzhiyun                        'dbusglib': 'dbus-glib',
79*4882a593Smuzhiyun                        'egl': 'virtual/egl',
80*4882a593Smuzhiyun                        'expat': 'expat',
81*4882a593Smuzhiyun                        'flex': 'flex-native',
82*4882a593Smuzhiyun                        'fontconfig': 'fontconfig',
83*4882a593Smuzhiyun                        'freetype': 'freetype',
84*4882a593Smuzhiyun                        'gettext': '',
85*4882a593Smuzhiyun                        'git': '',
86*4882a593Smuzhiyun                        'gio': 'glib-2.0',
87*4882a593Smuzhiyun                        'giounix': 'glib-2.0',
88*4882a593Smuzhiyun                        'glew': 'glew',
89*4882a593Smuzhiyun                        'glib': 'glib-2.0',
90*4882a593Smuzhiyun                        'glib2': 'glib-2.0',
91*4882a593Smuzhiyun                        'glu': 'libglu',
92*4882a593Smuzhiyun                        'glut': 'freeglut',
93*4882a593Smuzhiyun                        'gobject': 'glib-2.0',
94*4882a593Smuzhiyun                        'gperf': 'gperf-native',
95*4882a593Smuzhiyun                        'gnutls': 'gnutls',
96*4882a593Smuzhiyun                        'gtk2': 'gtk+',
97*4882a593Smuzhiyun                        'gtk3': 'gtk+3',
98*4882a593Smuzhiyun                        'gtk': 'gtk+3',
99*4882a593Smuzhiyun                        'harfbuzz': 'harfbuzz',
100*4882a593Smuzhiyun                        'icu': 'icu',
101*4882a593Smuzhiyun                        'intl': 'virtual/libintl',
102*4882a593Smuzhiyun                        'jpeg': 'jpeg',
103*4882a593Smuzhiyun                        'libarchive': 'libarchive',
104*4882a593Smuzhiyun                        'libiconv': 'virtual/libiconv',
105*4882a593Smuzhiyun                        'liblzma': 'xz',
106*4882a593Smuzhiyun                        'libxml2': 'libxml2',
107*4882a593Smuzhiyun                        'libxslt': 'libxslt',
108*4882a593Smuzhiyun                        'opengl': 'virtual/libgl',
109*4882a593Smuzhiyun                        'openmp': '',
110*4882a593Smuzhiyun                        'openssl': 'openssl',
111*4882a593Smuzhiyun                        'pango': 'pango',
112*4882a593Smuzhiyun                        'perl': '',
113*4882a593Smuzhiyun                        'perllibs': '',
114*4882a593Smuzhiyun                        'pkgconfig': '',
115*4882a593Smuzhiyun                        'png': 'libpng',
116*4882a593Smuzhiyun                        'pthread': '',
117*4882a593Smuzhiyun                        'pythoninterp': '',
118*4882a593Smuzhiyun                        'pythonlibs': '',
119*4882a593Smuzhiyun                        'ruby': 'ruby-native',
120*4882a593Smuzhiyun                        'sdl': 'libsdl',
121*4882a593Smuzhiyun                        'sdl2': 'libsdl2',
122*4882a593Smuzhiyun                        'subversion': 'subversion-native',
123*4882a593Smuzhiyun                        'swig': 'swig-native',
124*4882a593Smuzhiyun                        'tcl': 'tcl-native',
125*4882a593Smuzhiyun                        'threads': '',
126*4882a593Smuzhiyun                        'tiff': 'tiff',
127*4882a593Smuzhiyun                        'wget': 'wget',
128*4882a593Smuzhiyun                        'x11': 'libx11',
129*4882a593Smuzhiyun                        'xcb': 'libxcb',
130*4882a593Smuzhiyun                        'xext': 'libxext',
131*4882a593Smuzhiyun                        'xfixes': 'libxfixes',
132*4882a593Smuzhiyun                        'zlib': 'zlib',
133*4882a593Smuzhiyun                        }
134*4882a593Smuzhiyun
135*4882a593Smuzhiyun        pcdeps = []
136*4882a593Smuzhiyun        libdeps = []
137*4882a593Smuzhiyun        deps = []
138*4882a593Smuzhiyun        unmappedpkgs = []
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun        proj_re = re.compile('project\s*\(([^)]*)\)', re.IGNORECASE)
141*4882a593Smuzhiyun        pkgcm_re = re.compile('pkg_check_modules\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?\s+([^)\s]+)\s*\)', re.IGNORECASE)
142*4882a593Smuzhiyun        pkgsm_re = re.compile('pkg_search_module\s*\(\s*[a-zA-Z0-9-_]+\s*(REQUIRED)?((\s+[^)\s]+)+)\s*\)', re.IGNORECASE)
143*4882a593Smuzhiyun        findpackage_re = re.compile('find_package\s*\(\s*([a-zA-Z0-9-_]+)\s*.*', re.IGNORECASE)
144*4882a593Smuzhiyun        findlibrary_re = re.compile('find_library\s*\(\s*[a-zA-Z0-9-_]+\s*(NAMES\s+)?([a-zA-Z0-9-_ ]+)\s*.*')
145*4882a593Smuzhiyun        checklib_re = re.compile('check_library_exists\s*\(\s*([^\s)]+)\s*.*', re.IGNORECASE)
146*4882a593Smuzhiyun        include_re = re.compile('include\s*\(\s*([^)\s]*)\s*\)', re.IGNORECASE)
147*4882a593Smuzhiyun        subdir_re = re.compile('add_subdirectory\s*\(\s*([^)\s]*)\s*([^)\s]*)\s*\)', re.IGNORECASE)
148*4882a593Smuzhiyun        dep_re = re.compile('([^ ><=]+)( *[<>=]+ *[^ ><=]+)?')
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun        def find_cmake_package(pkg):
151*4882a593Smuzhiyun            RecipeHandler.load_devel_filemap(tinfoil.config_data)
152*4882a593Smuzhiyun            for fn, pn in RecipeHandler.recipecmakefilemap.items():
153*4882a593Smuzhiyun                splitname = fn.split('/')
154*4882a593Smuzhiyun                if len(splitname) > 1:
155*4882a593Smuzhiyun                    if splitname[0].lower().startswith(pkg.lower()):
156*4882a593Smuzhiyun                        if splitname[1] == '%s-config.cmake' % pkg.lower() or splitname[1] == '%sConfig.cmake' % pkg or splitname[1] == 'Find%s.cmake' % pkg:
157*4882a593Smuzhiyun                            return pn
158*4882a593Smuzhiyun            return None
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun        def interpret_value(value):
161*4882a593Smuzhiyun            return value.strip('"')
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun        def parse_cmake_file(fn, paths=None):
164*4882a593Smuzhiyun            searchpaths = (paths or []) + [os.path.dirname(fn)]
165*4882a593Smuzhiyun            logger.debug('Parsing file %s' % fn)
166*4882a593Smuzhiyun            with open(fn, 'r', errors='surrogateescape') as f:
167*4882a593Smuzhiyun                for line in f:
168*4882a593Smuzhiyun                    line = line.strip()
169*4882a593Smuzhiyun                    for handler in handlers:
170*4882a593Smuzhiyun                        if handler.process_line(srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values):
171*4882a593Smuzhiyun                            continue
172*4882a593Smuzhiyun                    res = include_re.match(line)
173*4882a593Smuzhiyun                    if res:
174*4882a593Smuzhiyun                        includefn = bb.utils.which(':'.join(searchpaths), res.group(1))
175*4882a593Smuzhiyun                        if includefn:
176*4882a593Smuzhiyun                            parse_cmake_file(includefn, searchpaths)
177*4882a593Smuzhiyun                        else:
178*4882a593Smuzhiyun                            logger.debug('Unable to recurse into include file %s' % res.group(1))
179*4882a593Smuzhiyun                        continue
180*4882a593Smuzhiyun                    res = subdir_re.match(line)
181*4882a593Smuzhiyun                    if res:
182*4882a593Smuzhiyun                        subdirfn = os.path.join(os.path.dirname(fn), res.group(1), 'CMakeLists.txt')
183*4882a593Smuzhiyun                        if os.path.exists(subdirfn):
184*4882a593Smuzhiyun                            parse_cmake_file(subdirfn, searchpaths)
185*4882a593Smuzhiyun                        else:
186*4882a593Smuzhiyun                            logger.debug('Unable to recurse into subdirectory file %s' % subdirfn)
187*4882a593Smuzhiyun                        continue
188*4882a593Smuzhiyun                    res = proj_re.match(line)
189*4882a593Smuzhiyun                    if res:
190*4882a593Smuzhiyun                        extravalues['PN'] = interpret_value(res.group(1).split()[0])
191*4882a593Smuzhiyun                        continue
192*4882a593Smuzhiyun                    res = pkgcm_re.match(line)
193*4882a593Smuzhiyun                    if res:
194*4882a593Smuzhiyun                        res = dep_re.findall(res.group(2))
195*4882a593Smuzhiyun                        if res:
196*4882a593Smuzhiyun                            pcdeps.extend([interpret_value(x[0]) for x in res])
197*4882a593Smuzhiyun                        inherits.append('pkgconfig')
198*4882a593Smuzhiyun                        continue
199*4882a593Smuzhiyun                    res = pkgsm_re.match(line)
200*4882a593Smuzhiyun                    if res:
201*4882a593Smuzhiyun                        res = dep_re.findall(res.group(2))
202*4882a593Smuzhiyun                        if res:
203*4882a593Smuzhiyun                            # Note: appending a tuple here!
204*4882a593Smuzhiyun                            item = tuple((interpret_value(x[0]) for x in res))
205*4882a593Smuzhiyun                            if len(item) == 1:
206*4882a593Smuzhiyun                                item = item[0]
207*4882a593Smuzhiyun                            pcdeps.append(item)
208*4882a593Smuzhiyun                        inherits.append('pkgconfig')
209*4882a593Smuzhiyun                        continue
210*4882a593Smuzhiyun                    res = findpackage_re.match(line)
211*4882a593Smuzhiyun                    if res:
212*4882a593Smuzhiyun                        origpkg = res.group(1)
213*4882a593Smuzhiyun                        pkg = interpret_value(origpkg)
214*4882a593Smuzhiyun                        found = False
215*4882a593Smuzhiyun                        for handler in handlers:
216*4882a593Smuzhiyun                            if handler.process_findpackage(srctree, fn, pkg, deps, outlines, inherits, values):
217*4882a593Smuzhiyun                                logger.debug('Mapped CMake package %s via handler %s' % (pkg, handler.__class__.__name__))
218*4882a593Smuzhiyun                                found = True
219*4882a593Smuzhiyun                                break
220*4882a593Smuzhiyun                        if found:
221*4882a593Smuzhiyun                            continue
222*4882a593Smuzhiyun                        elif pkg == 'Gettext':
223*4882a593Smuzhiyun                            inherits.append('gettext')
224*4882a593Smuzhiyun                        elif pkg == 'Perl':
225*4882a593Smuzhiyun                            inherits.append('perlnative')
226*4882a593Smuzhiyun                        elif pkg == 'PkgConfig':
227*4882a593Smuzhiyun                            inherits.append('pkgconfig')
228*4882a593Smuzhiyun                        elif pkg == 'PythonInterp':
229*4882a593Smuzhiyun                            inherits.append('python3native')
230*4882a593Smuzhiyun                        elif pkg == 'PythonLibs':
231*4882a593Smuzhiyun                            inherits.append('python3-dir')
232*4882a593Smuzhiyun                        else:
233*4882a593Smuzhiyun                            # Try to map via looking at installed CMake packages in pkgdata
234*4882a593Smuzhiyun                            dep = find_cmake_package(pkg)
235*4882a593Smuzhiyun                            if dep:
236*4882a593Smuzhiyun                                logger.debug('Mapped CMake package %s to recipe %s via pkgdata' % (pkg, dep))
237*4882a593Smuzhiyun                                deps.append(dep)
238*4882a593Smuzhiyun                            else:
239*4882a593Smuzhiyun                                dep = cmake_pkgmap.get(pkg.lower(), None)
240*4882a593Smuzhiyun                                if dep:
241*4882a593Smuzhiyun                                    logger.debug('Mapped CMake package %s to recipe %s via internal list' % (pkg, dep))
242*4882a593Smuzhiyun                                    deps.append(dep)
243*4882a593Smuzhiyun                                elif dep is None:
244*4882a593Smuzhiyun                                    unmappedpkgs.append(origpkg)
245*4882a593Smuzhiyun                        continue
246*4882a593Smuzhiyun                    res = checklib_re.match(line)
247*4882a593Smuzhiyun                    if res:
248*4882a593Smuzhiyun                        lib = interpret_value(res.group(1))
249*4882a593Smuzhiyun                        if not lib.startswith('$'):
250*4882a593Smuzhiyun                            libdeps.append(lib)
251*4882a593Smuzhiyun                    res = findlibrary_re.match(line)
252*4882a593Smuzhiyun                    if res:
253*4882a593Smuzhiyun                        libs = res.group(2).split()
254*4882a593Smuzhiyun                        for lib in libs:
255*4882a593Smuzhiyun                            if lib in ['HINTS', 'PATHS', 'PATH_SUFFIXES', 'DOC', 'NAMES_PER_DIR'] or lib.startswith(('NO_', 'CMAKE_', 'ONLY_CMAKE_')):
256*4882a593Smuzhiyun                                break
257*4882a593Smuzhiyun                            lib = interpret_value(lib)
258*4882a593Smuzhiyun                            if not lib.startswith('$'):
259*4882a593Smuzhiyun                                libdeps.append(lib)
260*4882a593Smuzhiyun                    if line.lower().startswith('useswig'):
261*4882a593Smuzhiyun                        deps.append('swig-native')
262*4882a593Smuzhiyun                        continue
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun        parse_cmake_file(srcfiles[0])
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun        if unmappedpkgs:
267*4882a593Smuzhiyun            outlines.append('# NOTE: unable to map the following CMake package dependencies: %s' % ' '.join(list(set(unmappedpkgs))))
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun        RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun        for handler in handlers:
272*4882a593Smuzhiyun            handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun        if inherits:
275*4882a593Smuzhiyun            values['inherit'] = ' '.join(list(set(inherits)))
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun        return values
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun
280*4882a593Smuzhiyunclass CmakeExtensionHandler(object):
281*4882a593Smuzhiyun    '''Base class for CMake extension handlers'''
282*4882a593Smuzhiyun    def process_line(self, srctree, fn, line, libdeps, pcdeps, deps, outlines, inherits, values):
283*4882a593Smuzhiyun        '''
284*4882a593Smuzhiyun        Handle a line parsed out of an CMake file.
285*4882a593Smuzhiyun        Return True if you've completely handled the passed in line, otherwise return False.
286*4882a593Smuzhiyun        '''
287*4882a593Smuzhiyun        return False
288*4882a593Smuzhiyun
289*4882a593Smuzhiyun    def process_findpackage(self, srctree, fn, pkg, deps, outlines, inherits, values):
290*4882a593Smuzhiyun        '''
291*4882a593Smuzhiyun        Handle a find_package package parsed out of a CMake file.
292*4882a593Smuzhiyun        Return True if you've completely handled the passed in package, otherwise return False.
293*4882a593Smuzhiyun        '''
294*4882a593Smuzhiyun        return False
295*4882a593Smuzhiyun
296*4882a593Smuzhiyun    def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values):
297*4882a593Smuzhiyun        '''
298*4882a593Smuzhiyun        Apply any desired post-processing on the output
299*4882a593Smuzhiyun        '''
300*4882a593Smuzhiyun        return
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun
303*4882a593Smuzhiyun
304*4882a593Smuzhiyunclass SconsRecipeHandler(RecipeHandler):
305*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
306*4882a593Smuzhiyun        if 'buildsystem' in handled:
307*4882a593Smuzhiyun            return False
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun        if RecipeHandler.checkfiles(srctree, ['SConstruct', 'Sconstruct', 'sconstruct']):
310*4882a593Smuzhiyun            classes.append('scons')
311*4882a593Smuzhiyun            lines_after.append('# Specify any options you want to pass to scons using EXTRA_OESCONS:')
312*4882a593Smuzhiyun            lines_after.append('EXTRA_OESCONS = ""')
313*4882a593Smuzhiyun            lines_after.append('')
314*4882a593Smuzhiyun            handled.append('buildsystem')
315*4882a593Smuzhiyun            return True
316*4882a593Smuzhiyun        return False
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun
319*4882a593Smuzhiyunclass QmakeRecipeHandler(RecipeHandler):
320*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
321*4882a593Smuzhiyun        if 'buildsystem' in handled:
322*4882a593Smuzhiyun            return False
323*4882a593Smuzhiyun
324*4882a593Smuzhiyun        if RecipeHandler.checkfiles(srctree, ['*.pro']):
325*4882a593Smuzhiyun            classes.append('qmake2')
326*4882a593Smuzhiyun            handled.append('buildsystem')
327*4882a593Smuzhiyun            return True
328*4882a593Smuzhiyun        return False
329*4882a593Smuzhiyun
330*4882a593Smuzhiyun
331*4882a593Smuzhiyunclass AutotoolsRecipeHandler(RecipeHandler):
332*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
333*4882a593Smuzhiyun        if 'buildsystem' in handled:
334*4882a593Smuzhiyun            return False
335*4882a593Smuzhiyun
336*4882a593Smuzhiyun        autoconf = False
337*4882a593Smuzhiyun        if RecipeHandler.checkfiles(srctree, ['configure.ac', 'configure.in']):
338*4882a593Smuzhiyun            autoconf = True
339*4882a593Smuzhiyun            values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, extravalues)
340*4882a593Smuzhiyun            classes.extend(values.pop('inherit', '').split())
341*4882a593Smuzhiyun            for var, value in values.items():
342*4882a593Smuzhiyun                lines_before.append('%s = "%s"' % (var, value))
343*4882a593Smuzhiyun        else:
344*4882a593Smuzhiyun            conffile = RecipeHandler.checkfiles(srctree, ['configure'])
345*4882a593Smuzhiyun            if conffile:
346*4882a593Smuzhiyun                # Check if this is just a pre-generated autoconf configure script
347*4882a593Smuzhiyun                with open(conffile[0], 'r', errors='surrogateescape') as f:
348*4882a593Smuzhiyun                    for i in range(1, 10):
349*4882a593Smuzhiyun                        if 'Generated by GNU Autoconf' in f.readline():
350*4882a593Smuzhiyun                            autoconf = True
351*4882a593Smuzhiyun                            break
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun        if autoconf and not ('PV' in extravalues and 'PN' in extravalues):
354*4882a593Smuzhiyun            # Last resort
355*4882a593Smuzhiyun            conffile = RecipeHandler.checkfiles(srctree, ['configure'])
356*4882a593Smuzhiyun            if conffile:
357*4882a593Smuzhiyun                with open(conffile[0], 'r', errors='surrogateescape') as f:
358*4882a593Smuzhiyun                    for line in f:
359*4882a593Smuzhiyun                        line = line.strip()
360*4882a593Smuzhiyun                        if line.startswith('VERSION=') or line.startswith('PACKAGE_VERSION='):
361*4882a593Smuzhiyun                            pv = line.split('=')[1].strip('"\'')
362*4882a593Smuzhiyun                            if pv and not 'PV' in extravalues and validate_pv(pv):
363*4882a593Smuzhiyun                                extravalues['PV'] = pv
364*4882a593Smuzhiyun                        elif line.startswith('PACKAGE_NAME=') or line.startswith('PACKAGE='):
365*4882a593Smuzhiyun                            pn = line.split('=')[1].strip('"\'')
366*4882a593Smuzhiyun                            if pn and not 'PN' in extravalues:
367*4882a593Smuzhiyun                                extravalues['PN'] = pn
368*4882a593Smuzhiyun
369*4882a593Smuzhiyun        if autoconf:
370*4882a593Smuzhiyun            lines_before.append('')
371*4882a593Smuzhiyun            lines_before.append('# NOTE: if this software is not capable of being built in a separate build directory')
372*4882a593Smuzhiyun            lines_before.append('# from the source, you should replace autotools with autotools-brokensep in the')
373*4882a593Smuzhiyun            lines_before.append('# inherit line')
374*4882a593Smuzhiyun            classes.append('autotools')
375*4882a593Smuzhiyun            lines_after.append('# Specify any options you want to pass to the configure script using EXTRA_OECONF:')
376*4882a593Smuzhiyun            lines_after.append('EXTRA_OECONF = ""')
377*4882a593Smuzhiyun            lines_after.append('')
378*4882a593Smuzhiyun            handled.append('buildsystem')
379*4882a593Smuzhiyun            return True
380*4882a593Smuzhiyun
381*4882a593Smuzhiyun        return False
382*4882a593Smuzhiyun
383*4882a593Smuzhiyun    @staticmethod
384*4882a593Smuzhiyun    def extract_autotools_deps(outlines, srctree, extravalues=None, acfile=None):
385*4882a593Smuzhiyun        import shlex
386*4882a593Smuzhiyun
387*4882a593Smuzhiyun        # Find all plugins that want to register handlers
388*4882a593Smuzhiyun        logger.debug('Loading autotools handlers')
389*4882a593Smuzhiyun        handlers = []
390*4882a593Smuzhiyun        for plugin in plugins:
391*4882a593Smuzhiyun            if hasattr(plugin, 'register_autotools_handlers'):
392*4882a593Smuzhiyun                plugin.register_autotools_handlers(handlers)
393*4882a593Smuzhiyun
394*4882a593Smuzhiyun        values = {}
395*4882a593Smuzhiyun        inherits = []
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun        # Hardcoded map, we also use a dynamic one based on what's in the sysroot
398*4882a593Smuzhiyun        progmap = {'flex': 'flex-native',
399*4882a593Smuzhiyun                'bison': 'bison-native',
400*4882a593Smuzhiyun                'm4': 'm4-native',
401*4882a593Smuzhiyun                'tar': 'tar-native',
402*4882a593Smuzhiyun                'ar': 'binutils-native',
403*4882a593Smuzhiyun                'ranlib': 'binutils-native',
404*4882a593Smuzhiyun                'ld': 'binutils-native',
405*4882a593Smuzhiyun                'strip': 'binutils-native',
406*4882a593Smuzhiyun                'libtool': '',
407*4882a593Smuzhiyun                'autoconf': '',
408*4882a593Smuzhiyun                'autoheader': '',
409*4882a593Smuzhiyun                'automake': '',
410*4882a593Smuzhiyun                'uname': '',
411*4882a593Smuzhiyun                'rm': '',
412*4882a593Smuzhiyun                'cp': '',
413*4882a593Smuzhiyun                'mv': '',
414*4882a593Smuzhiyun                'find': '',
415*4882a593Smuzhiyun                'awk': '',
416*4882a593Smuzhiyun                'sed': '',
417*4882a593Smuzhiyun                }
418*4882a593Smuzhiyun        progclassmap = {'gconftool-2': 'gconf',
419*4882a593Smuzhiyun                'pkg-config': 'pkgconfig',
420*4882a593Smuzhiyun                'python': 'python3native',
421*4882a593Smuzhiyun                'python3': 'python3native',
422*4882a593Smuzhiyun                'perl': 'perlnative',
423*4882a593Smuzhiyun                'makeinfo': 'texinfo',
424*4882a593Smuzhiyun                }
425*4882a593Smuzhiyun
426*4882a593Smuzhiyun        pkg_re = re.compile('PKG_CHECK_MODULES\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*')
427*4882a593Smuzhiyun        pkgce_re = re.compile('PKG_CHECK_EXISTS\(\s*\[?([^,\]]*)\]?[),].*')
428*4882a593Smuzhiyun        lib_re = re.compile('AC_CHECK_LIB\(\s*\[?([^,\]]*)\]?,.*')
429*4882a593Smuzhiyun        libx_re = re.compile('AX_CHECK_LIBRARY\(\s*\[?[^,\]]*\]?,\s*\[?([^,\]]*)\]?,\s*\[?([a-zA-Z0-9-]*)\]?,.*')
430*4882a593Smuzhiyun        progs_re = re.compile('_PROGS?\(\s*\[?[a-zA-Z0-9_]*\]?,\s*\[?([^,\]]*)\]?[),].*')
431*4882a593Smuzhiyun        dep_re = re.compile('([^ ><=]+)( [<>=]+ [^ ><=]+)?')
432*4882a593Smuzhiyun        ac_init_re = re.compile('AC_INIT\(\s*([^,]+),\s*([^,]+)[,)].*')
433*4882a593Smuzhiyun        am_init_re = re.compile('AM_INIT_AUTOMAKE\(\s*([^,]+),\s*([^,]+)[,)].*')
434*4882a593Smuzhiyun        define_re = re.compile('\s*(m4_)?define\(\s*([^,]+),\s*([^,]+)\)')
435*4882a593Smuzhiyun        version_re = re.compile('([0-9.]+)')
436*4882a593Smuzhiyun
437*4882a593Smuzhiyun        defines = {}
438*4882a593Smuzhiyun        def subst_defines(value):
439*4882a593Smuzhiyun            newvalue = value
440*4882a593Smuzhiyun            for define, defval in defines.items():
441*4882a593Smuzhiyun                newvalue = newvalue.replace(define, defval)
442*4882a593Smuzhiyun            if newvalue != value:
443*4882a593Smuzhiyun                return subst_defines(newvalue)
444*4882a593Smuzhiyun            return value
445*4882a593Smuzhiyun
446*4882a593Smuzhiyun        def process_value(value):
447*4882a593Smuzhiyun            value = value.replace('[', '').replace(']', '')
448*4882a593Smuzhiyun            if value.startswith('m4_esyscmd(') or value.startswith('m4_esyscmd_s('):
449*4882a593Smuzhiyun                cmd = subst_defines(value[value.index('(')+1:-1])
450*4882a593Smuzhiyun                try:
451*4882a593Smuzhiyun                    if '|' in cmd:
452*4882a593Smuzhiyun                        cmd = 'set -o pipefail; ' + cmd
453*4882a593Smuzhiyun                    stdout, _ = bb.process.run(cmd, cwd=srctree, shell=True)
454*4882a593Smuzhiyun                    ret = stdout.rstrip()
455*4882a593Smuzhiyun                except bb.process.ExecutionError as e:
456*4882a593Smuzhiyun                    ret = ''
457*4882a593Smuzhiyun            elif value.startswith('m4_'):
458*4882a593Smuzhiyun                return None
459*4882a593Smuzhiyun            ret = subst_defines(value)
460*4882a593Smuzhiyun            if ret:
461*4882a593Smuzhiyun                ret = ret.strip('"\'')
462*4882a593Smuzhiyun            return ret
463*4882a593Smuzhiyun
464*4882a593Smuzhiyun        # Since a configure.ac file is essentially a program, this is only ever going to be
465*4882a593Smuzhiyun        # a hack unfortunately; but it ought to be enough of an approximation
466*4882a593Smuzhiyun        if acfile:
467*4882a593Smuzhiyun            srcfiles = [acfile]
468*4882a593Smuzhiyun        else:
469*4882a593Smuzhiyun            srcfiles = RecipeHandler.checkfiles(srctree, ['acinclude.m4', 'configure.ac', 'configure.in'])
470*4882a593Smuzhiyun
471*4882a593Smuzhiyun        pcdeps = []
472*4882a593Smuzhiyun        libdeps = []
473*4882a593Smuzhiyun        deps = []
474*4882a593Smuzhiyun        unmapped = []
475*4882a593Smuzhiyun
476*4882a593Smuzhiyun        RecipeHandler.load_binmap(tinfoil.config_data)
477*4882a593Smuzhiyun
478*4882a593Smuzhiyun        def process_macro(keyword, value):
479*4882a593Smuzhiyun            for handler in handlers:
480*4882a593Smuzhiyun                if handler.process_macro(srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
481*4882a593Smuzhiyun                    return
482*4882a593Smuzhiyun            logger.debug('Found keyword %s with value "%s"' % (keyword, value))
483*4882a593Smuzhiyun            if keyword == 'PKG_CHECK_MODULES':
484*4882a593Smuzhiyun                res = pkg_re.search(value)
485*4882a593Smuzhiyun                if res:
486*4882a593Smuzhiyun                    res = dep_re.findall(res.group(1))
487*4882a593Smuzhiyun                    if res:
488*4882a593Smuzhiyun                        pcdeps.extend([x[0] for x in res])
489*4882a593Smuzhiyun                inherits.append('pkgconfig')
490*4882a593Smuzhiyun            elif keyword == 'PKG_CHECK_EXISTS':
491*4882a593Smuzhiyun                res = pkgce_re.search(value)
492*4882a593Smuzhiyun                if res:
493*4882a593Smuzhiyun                    res = dep_re.findall(res.group(1))
494*4882a593Smuzhiyun                    if res:
495*4882a593Smuzhiyun                        pcdeps.extend([x[0] for x in res])
496*4882a593Smuzhiyun                inherits.append('pkgconfig')
497*4882a593Smuzhiyun            elif keyword in ('AM_GNU_GETTEXT', 'AM_GLIB_GNU_GETTEXT', 'GETTEXT_PACKAGE'):
498*4882a593Smuzhiyun                inherits.append('gettext')
499*4882a593Smuzhiyun            elif keyword in ('AC_PROG_INTLTOOL', 'IT_PROG_INTLTOOL'):
500*4882a593Smuzhiyun                deps.append('intltool-native')
501*4882a593Smuzhiyun            elif keyword == 'AM_PATH_GLIB_2_0':
502*4882a593Smuzhiyun                deps.append('glib-2.0')
503*4882a593Smuzhiyun            elif keyword in ('AC_CHECK_PROG', 'AC_PATH_PROG', 'AX_WITH_PROG'):
504*4882a593Smuzhiyun                res = progs_re.search(value)
505*4882a593Smuzhiyun                if res:
506*4882a593Smuzhiyun                    for prog in shlex.split(res.group(1)):
507*4882a593Smuzhiyun                        prog = prog.split()[0]
508*4882a593Smuzhiyun                        for handler in handlers:
509*4882a593Smuzhiyun                            if handler.process_prog(srctree, keyword, value, prog, deps, outlines, inherits, values):
510*4882a593Smuzhiyun                                return
511*4882a593Smuzhiyun                        progclass = progclassmap.get(prog, None)
512*4882a593Smuzhiyun                        if progclass:
513*4882a593Smuzhiyun                            inherits.append(progclass)
514*4882a593Smuzhiyun                        else:
515*4882a593Smuzhiyun                            progdep = RecipeHandler.recipebinmap.get(prog, None)
516*4882a593Smuzhiyun                            if not progdep:
517*4882a593Smuzhiyun                                progdep = progmap.get(prog, None)
518*4882a593Smuzhiyun                            if progdep:
519*4882a593Smuzhiyun                                deps.append(progdep)
520*4882a593Smuzhiyun                            elif progdep is None:
521*4882a593Smuzhiyun                                if not prog.startswith('$'):
522*4882a593Smuzhiyun                                    unmapped.append(prog)
523*4882a593Smuzhiyun            elif keyword == 'AC_CHECK_LIB':
524*4882a593Smuzhiyun                res = lib_re.search(value)
525*4882a593Smuzhiyun                if res:
526*4882a593Smuzhiyun                    lib = res.group(1)
527*4882a593Smuzhiyun                    if not lib.startswith('$'):
528*4882a593Smuzhiyun                        libdeps.append(lib)
529*4882a593Smuzhiyun            elif keyword == 'AX_CHECK_LIBRARY':
530*4882a593Smuzhiyun                res = libx_re.search(value)
531*4882a593Smuzhiyun                if res:
532*4882a593Smuzhiyun                    lib = res.group(2)
533*4882a593Smuzhiyun                    if not lib.startswith('$'):
534*4882a593Smuzhiyun                        header = res.group(1)
535*4882a593Smuzhiyun                        libdeps.append((lib, header))
536*4882a593Smuzhiyun            elif keyword == 'AC_PATH_X':
537*4882a593Smuzhiyun                deps.append('libx11')
538*4882a593Smuzhiyun            elif keyword in ('AX_BOOST', 'BOOST_REQUIRE'):
539*4882a593Smuzhiyun                deps.append('boost')
540*4882a593Smuzhiyun            elif keyword in ('AC_PROG_LEX', 'AM_PROG_LEX', 'AX_PROG_FLEX'):
541*4882a593Smuzhiyun                deps.append('flex-native')
542*4882a593Smuzhiyun            elif keyword in ('AC_PROG_YACC', 'AX_PROG_BISON'):
543*4882a593Smuzhiyun                deps.append('bison-native')
544*4882a593Smuzhiyun            elif keyword == 'AX_CHECK_ZLIB':
545*4882a593Smuzhiyun                deps.append('zlib')
546*4882a593Smuzhiyun            elif keyword in ('AX_CHECK_OPENSSL', 'AX_LIB_CRYPTO'):
547*4882a593Smuzhiyun                deps.append('openssl')
548*4882a593Smuzhiyun            elif keyword in ('AX_LIB_CURL', 'LIBCURL_CHECK_CONFIG'):
549*4882a593Smuzhiyun                deps.append('curl')
550*4882a593Smuzhiyun            elif keyword == 'AX_LIB_BEECRYPT':
551*4882a593Smuzhiyun                deps.append('beecrypt')
552*4882a593Smuzhiyun            elif keyword == 'AX_LIB_EXPAT':
553*4882a593Smuzhiyun                deps.append('expat')
554*4882a593Smuzhiyun            elif keyword == 'AX_LIB_GCRYPT':
555*4882a593Smuzhiyun                deps.append('libgcrypt')
556*4882a593Smuzhiyun            elif keyword == 'AX_LIB_NETTLE':
557*4882a593Smuzhiyun                deps.append('nettle')
558*4882a593Smuzhiyun            elif keyword == 'AX_LIB_READLINE':
559*4882a593Smuzhiyun                deps.append('readline')
560*4882a593Smuzhiyun            elif keyword == 'AX_LIB_SQLITE3':
561*4882a593Smuzhiyun                deps.append('sqlite3')
562*4882a593Smuzhiyun            elif keyword == 'AX_LIB_TAGLIB':
563*4882a593Smuzhiyun                deps.append('taglib')
564*4882a593Smuzhiyun            elif keyword in ['AX_PKG_SWIG', 'AC_PROG_SWIG']:
565*4882a593Smuzhiyun                deps.append('swig-native')
566*4882a593Smuzhiyun            elif keyword == 'AX_PROG_XSLTPROC':
567*4882a593Smuzhiyun                deps.append('libxslt-native')
568*4882a593Smuzhiyun            elif keyword in ['AC_PYTHON_DEVEL', 'AX_PYTHON_DEVEL', 'AM_PATH_PYTHON']:
569*4882a593Smuzhiyun                pythonclass = 'python3native'
570*4882a593Smuzhiyun            elif keyword == 'AX_WITH_CURSES':
571*4882a593Smuzhiyun                deps.append('ncurses')
572*4882a593Smuzhiyun            elif keyword == 'AX_PATH_BDB':
573*4882a593Smuzhiyun                deps.append('db')
574*4882a593Smuzhiyun            elif keyword == 'AX_PATH_LIB_PCRE':
575*4882a593Smuzhiyun                deps.append('libpcre')
576*4882a593Smuzhiyun            elif keyword == 'AC_INIT':
577*4882a593Smuzhiyun                if extravalues is not None:
578*4882a593Smuzhiyun                    res = ac_init_re.match(value)
579*4882a593Smuzhiyun                    if res:
580*4882a593Smuzhiyun                        extravalues['PN'] = process_value(res.group(1))
581*4882a593Smuzhiyun                        pv = process_value(res.group(2))
582*4882a593Smuzhiyun                        if validate_pv(pv):
583*4882a593Smuzhiyun                            extravalues['PV'] = pv
584*4882a593Smuzhiyun            elif keyword == 'AM_INIT_AUTOMAKE':
585*4882a593Smuzhiyun                if extravalues is not None:
586*4882a593Smuzhiyun                    if 'PN' not in extravalues:
587*4882a593Smuzhiyun                        res = am_init_re.match(value)
588*4882a593Smuzhiyun                        if res:
589*4882a593Smuzhiyun                            if res.group(1) != 'AC_PACKAGE_NAME':
590*4882a593Smuzhiyun                                extravalues['PN'] = process_value(res.group(1))
591*4882a593Smuzhiyun                            pv = process_value(res.group(2))
592*4882a593Smuzhiyun                            if validate_pv(pv):
593*4882a593Smuzhiyun                                extravalues['PV'] = pv
594*4882a593Smuzhiyun            elif keyword == 'define(':
595*4882a593Smuzhiyun                res = define_re.match(value)
596*4882a593Smuzhiyun                if res:
597*4882a593Smuzhiyun                    key = res.group(2).strip('[]')
598*4882a593Smuzhiyun                    value = process_value(res.group(3))
599*4882a593Smuzhiyun                    if value is not None:
600*4882a593Smuzhiyun                        defines[key] = value
601*4882a593Smuzhiyun
602*4882a593Smuzhiyun        keywords = ['PKG_CHECK_MODULES',
603*4882a593Smuzhiyun                    'PKG_CHECK_EXISTS',
604*4882a593Smuzhiyun                    'AM_GNU_GETTEXT',
605*4882a593Smuzhiyun                    'AM_GLIB_GNU_GETTEXT',
606*4882a593Smuzhiyun                    'GETTEXT_PACKAGE',
607*4882a593Smuzhiyun                    'AC_PROG_INTLTOOL',
608*4882a593Smuzhiyun                    'IT_PROG_INTLTOOL',
609*4882a593Smuzhiyun                    'AM_PATH_GLIB_2_0',
610*4882a593Smuzhiyun                    'AC_CHECK_PROG',
611*4882a593Smuzhiyun                    'AC_PATH_PROG',
612*4882a593Smuzhiyun                    'AX_WITH_PROG',
613*4882a593Smuzhiyun                    'AC_CHECK_LIB',
614*4882a593Smuzhiyun                    'AX_CHECK_LIBRARY',
615*4882a593Smuzhiyun                    'AC_PATH_X',
616*4882a593Smuzhiyun                    'AX_BOOST',
617*4882a593Smuzhiyun                    'BOOST_REQUIRE',
618*4882a593Smuzhiyun                    'AC_PROG_LEX',
619*4882a593Smuzhiyun                    'AM_PROG_LEX',
620*4882a593Smuzhiyun                    'AX_PROG_FLEX',
621*4882a593Smuzhiyun                    'AC_PROG_YACC',
622*4882a593Smuzhiyun                    'AX_PROG_BISON',
623*4882a593Smuzhiyun                    'AX_CHECK_ZLIB',
624*4882a593Smuzhiyun                    'AX_CHECK_OPENSSL',
625*4882a593Smuzhiyun                    'AX_LIB_CRYPTO',
626*4882a593Smuzhiyun                    'AX_LIB_CURL',
627*4882a593Smuzhiyun                    'LIBCURL_CHECK_CONFIG',
628*4882a593Smuzhiyun                    'AX_LIB_BEECRYPT',
629*4882a593Smuzhiyun                    'AX_LIB_EXPAT',
630*4882a593Smuzhiyun                    'AX_LIB_GCRYPT',
631*4882a593Smuzhiyun                    'AX_LIB_NETTLE',
632*4882a593Smuzhiyun                    'AX_LIB_READLINE'
633*4882a593Smuzhiyun                    'AX_LIB_SQLITE3',
634*4882a593Smuzhiyun                    'AX_LIB_TAGLIB',
635*4882a593Smuzhiyun                    'AX_PKG_SWIG',
636*4882a593Smuzhiyun                    'AC_PROG_SWIG',
637*4882a593Smuzhiyun                    'AX_PROG_XSLTPROC',
638*4882a593Smuzhiyun                    'AC_PYTHON_DEVEL',
639*4882a593Smuzhiyun                    'AX_PYTHON_DEVEL',
640*4882a593Smuzhiyun                    'AM_PATH_PYTHON',
641*4882a593Smuzhiyun                    'AX_WITH_CURSES',
642*4882a593Smuzhiyun                    'AX_PATH_BDB',
643*4882a593Smuzhiyun                    'AX_PATH_LIB_PCRE',
644*4882a593Smuzhiyun                    'AC_INIT',
645*4882a593Smuzhiyun                    'AM_INIT_AUTOMAKE',
646*4882a593Smuzhiyun                    'define(',
647*4882a593Smuzhiyun                    ]
648*4882a593Smuzhiyun
649*4882a593Smuzhiyun        for handler in handlers:
650*4882a593Smuzhiyun            handler.extend_keywords(keywords)
651*4882a593Smuzhiyun
652*4882a593Smuzhiyun        for srcfile in srcfiles:
653*4882a593Smuzhiyun            nesting = 0
654*4882a593Smuzhiyun            in_keyword = ''
655*4882a593Smuzhiyun            partial = ''
656*4882a593Smuzhiyun            with open(srcfile, 'r', errors='surrogateescape') as f:
657*4882a593Smuzhiyun                for line in f:
658*4882a593Smuzhiyun                    if in_keyword:
659*4882a593Smuzhiyun                        partial += ' ' + line.strip()
660*4882a593Smuzhiyun                        if partial.endswith('\\'):
661*4882a593Smuzhiyun                            partial = partial[:-1]
662*4882a593Smuzhiyun                        nesting = nesting + line.count('(') - line.count(')')
663*4882a593Smuzhiyun                        if nesting == 0:
664*4882a593Smuzhiyun                            process_macro(in_keyword, partial)
665*4882a593Smuzhiyun                            partial = ''
666*4882a593Smuzhiyun                            in_keyword = ''
667*4882a593Smuzhiyun                    else:
668*4882a593Smuzhiyun                        for keyword in keywords:
669*4882a593Smuzhiyun                            if keyword in line:
670*4882a593Smuzhiyun                                nesting = line.count('(') - line.count(')')
671*4882a593Smuzhiyun                                if nesting > 0:
672*4882a593Smuzhiyun                                    partial = line.strip()
673*4882a593Smuzhiyun                                    if partial.endswith('\\'):
674*4882a593Smuzhiyun                                        partial = partial[:-1]
675*4882a593Smuzhiyun                                    in_keyword = keyword
676*4882a593Smuzhiyun                                else:
677*4882a593Smuzhiyun                                    process_macro(keyword, line.strip())
678*4882a593Smuzhiyun                                break
679*4882a593Smuzhiyun
680*4882a593Smuzhiyun            if in_keyword:
681*4882a593Smuzhiyun                process_macro(in_keyword, partial)
682*4882a593Smuzhiyun
683*4882a593Smuzhiyun        if extravalues:
684*4882a593Smuzhiyun            for k,v in list(extravalues.items()):
685*4882a593Smuzhiyun                if v:
686*4882a593Smuzhiyun                    if v.startswith('$') or v.startswith('@') or v.startswith('%'):
687*4882a593Smuzhiyun                        del extravalues[k]
688*4882a593Smuzhiyun                    else:
689*4882a593Smuzhiyun                        extravalues[k] = v.strip('"\'').rstrip('()')
690*4882a593Smuzhiyun
691*4882a593Smuzhiyun        if unmapped:
692*4882a593Smuzhiyun            outlines.append('# NOTE: the following prog dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmapped))))
693*4882a593Smuzhiyun
694*4882a593Smuzhiyun        RecipeHandler.handle_depends(libdeps, pcdeps, deps, outlines, values, tinfoil.config_data)
695*4882a593Smuzhiyun
696*4882a593Smuzhiyun        for handler in handlers:
697*4882a593Smuzhiyun            handler.post_process(srctree, libdeps, pcdeps, deps, outlines, inherits, values)
698*4882a593Smuzhiyun
699*4882a593Smuzhiyun        if inherits:
700*4882a593Smuzhiyun            values['inherit'] = ' '.join(list(set(inherits)))
701*4882a593Smuzhiyun
702*4882a593Smuzhiyun        return values
703*4882a593Smuzhiyun
704*4882a593Smuzhiyun
705*4882a593Smuzhiyunclass AutotoolsExtensionHandler(object):
706*4882a593Smuzhiyun    '''Base class for Autotools extension handlers'''
707*4882a593Smuzhiyun    def process_macro(self, srctree, keyword, value, process_value, libdeps, pcdeps, deps, outlines, inherits, values):
708*4882a593Smuzhiyun        '''
709*4882a593Smuzhiyun        Handle a macro parsed out of an autotools file. Note that if you want this to be called
710*4882a593Smuzhiyun        for any macro other than the ones AutotoolsRecipeHandler already looks for, you'll need
711*4882a593Smuzhiyun        to add it to the keywords list in extend_keywords().
712*4882a593Smuzhiyun        Return True if you've completely handled the passed in macro, otherwise return False.
713*4882a593Smuzhiyun        '''
714*4882a593Smuzhiyun        return False
715*4882a593Smuzhiyun
716*4882a593Smuzhiyun    def extend_keywords(self, keywords):
717*4882a593Smuzhiyun        '''Adds keywords to be recognised by the parser (so that you get a call to process_macro)'''
718*4882a593Smuzhiyun        return
719*4882a593Smuzhiyun
720*4882a593Smuzhiyun    def process_prog(self, srctree, keyword, value, prog, deps, outlines, inherits, values):
721*4882a593Smuzhiyun        '''
722*4882a593Smuzhiyun        Handle an AC_PATH_PROG, AC_CHECK_PROG etc. line
723*4882a593Smuzhiyun        Return True if you've completely handled the passed in macro, otherwise return False.
724*4882a593Smuzhiyun        '''
725*4882a593Smuzhiyun        return False
726*4882a593Smuzhiyun
727*4882a593Smuzhiyun    def post_process(self, srctree, fn, pkg, deps, outlines, inherits, values):
728*4882a593Smuzhiyun        '''
729*4882a593Smuzhiyun        Apply any desired post-processing on the output
730*4882a593Smuzhiyun        '''
731*4882a593Smuzhiyun        return
732*4882a593Smuzhiyun
733*4882a593Smuzhiyun
734*4882a593Smuzhiyunclass MakefileRecipeHandler(RecipeHandler):
735*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
736*4882a593Smuzhiyun        if 'buildsystem' in handled:
737*4882a593Smuzhiyun            return False
738*4882a593Smuzhiyun
739*4882a593Smuzhiyun        makefile = RecipeHandler.checkfiles(srctree, ['Makefile', 'makefile', 'GNUmakefile'])
740*4882a593Smuzhiyun        if makefile:
741*4882a593Smuzhiyun            lines_after.append('# NOTE: this is a Makefile-only piece of software, so we cannot generate much of the')
742*4882a593Smuzhiyun            lines_after.append('# recipe automatically - you will need to examine the Makefile yourself and ensure')
743*4882a593Smuzhiyun            lines_after.append('# that the appropriate arguments are passed in.')
744*4882a593Smuzhiyun            lines_after.append('')
745*4882a593Smuzhiyun
746*4882a593Smuzhiyun            scanfile = os.path.join(srctree, 'configure.scan')
747*4882a593Smuzhiyun            skipscan = False
748*4882a593Smuzhiyun            try:
749*4882a593Smuzhiyun                stdout, stderr = bb.process.run('autoscan', cwd=srctree, shell=True)
750*4882a593Smuzhiyun            except bb.process.ExecutionError as e:
751*4882a593Smuzhiyun                skipscan = True
752*4882a593Smuzhiyun            if scanfile and os.path.exists(scanfile):
753*4882a593Smuzhiyun                values = AutotoolsRecipeHandler.extract_autotools_deps(lines_before, srctree, acfile=scanfile)
754*4882a593Smuzhiyun                classes.extend(values.pop('inherit', '').split())
755*4882a593Smuzhiyun                for var, value in values.items():
756*4882a593Smuzhiyun                    if var == 'DEPENDS':
757*4882a593Smuzhiyun                        lines_before.append('# NOTE: some of these dependencies may be optional, check the Makefile and/or upstream documentation')
758*4882a593Smuzhiyun                    lines_before.append('%s = "%s"' % (var, value))
759*4882a593Smuzhiyun                lines_before.append('')
760*4882a593Smuzhiyun                for f in ['configure.scan', 'autoscan.log']:
761*4882a593Smuzhiyun                    fp = os.path.join(srctree, f)
762*4882a593Smuzhiyun                    if os.path.exists(fp):
763*4882a593Smuzhiyun                        os.remove(fp)
764*4882a593Smuzhiyun
765*4882a593Smuzhiyun            self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])
766*4882a593Smuzhiyun
767*4882a593Smuzhiyun            func = []
768*4882a593Smuzhiyun            func.append('# You will almost certainly need to add additional arguments here')
769*4882a593Smuzhiyun            func.append('oe_runmake')
770*4882a593Smuzhiyun            self.genfunction(lines_after, 'do_compile', func)
771*4882a593Smuzhiyun
772*4882a593Smuzhiyun            installtarget = True
773*4882a593Smuzhiyun            try:
774*4882a593Smuzhiyun                stdout, stderr = bb.process.run('make -n install', cwd=srctree, shell=True)
775*4882a593Smuzhiyun            except bb.process.ExecutionError as e:
776*4882a593Smuzhiyun                if e.exitcode != 1:
777*4882a593Smuzhiyun                    installtarget = False
778*4882a593Smuzhiyun            func = []
779*4882a593Smuzhiyun            if installtarget:
780*4882a593Smuzhiyun                func.append('# This is a guess; additional arguments may be required')
781*4882a593Smuzhiyun                makeargs = ''
782*4882a593Smuzhiyun                with open(makefile[0], 'r', errors='surrogateescape') as f:
783*4882a593Smuzhiyun                    for i in range(1, 100):
784*4882a593Smuzhiyun                        if 'DESTDIR' in f.readline():
785*4882a593Smuzhiyun                            makeargs += " 'DESTDIR=${D}'"
786*4882a593Smuzhiyun                            break
787*4882a593Smuzhiyun                func.append('oe_runmake install%s' % makeargs)
788*4882a593Smuzhiyun            else:
789*4882a593Smuzhiyun                func.append('# NOTE: unable to determine what to put here - there is a Makefile but no')
790*4882a593Smuzhiyun                func.append('# target named "install", so you will need to define this yourself')
791*4882a593Smuzhiyun            self.genfunction(lines_after, 'do_install', func)
792*4882a593Smuzhiyun
793*4882a593Smuzhiyun            handled.append('buildsystem')
794*4882a593Smuzhiyun        else:
795*4882a593Smuzhiyun            lines_after.append('# NOTE: no Makefile found, unable to determine what needs to be done')
796*4882a593Smuzhiyun            lines_after.append('')
797*4882a593Smuzhiyun            self.genfunction(lines_after, 'do_configure', ['# Specify any needed configure commands here'])
798*4882a593Smuzhiyun            self.genfunction(lines_after, 'do_compile', ['# Specify compilation commands here'])
799*4882a593Smuzhiyun            self.genfunction(lines_after, 'do_install', ['# Specify install commands here'])
800*4882a593Smuzhiyun
801*4882a593Smuzhiyun
802*4882a593Smuzhiyunclass VersionFileRecipeHandler(RecipeHandler):
803*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
804*4882a593Smuzhiyun        if 'PV' not in extravalues:
805*4882a593Smuzhiyun            # Look for a VERSION or version file containing a single line consisting
806*4882a593Smuzhiyun            # only of a version number
807*4882a593Smuzhiyun            filelist = RecipeHandler.checkfiles(srctree, ['VERSION', 'version'])
808*4882a593Smuzhiyun            version = None
809*4882a593Smuzhiyun            for fileitem in filelist:
810*4882a593Smuzhiyun                linecount = 0
811*4882a593Smuzhiyun                with open(fileitem, 'r', errors='surrogateescape') as f:
812*4882a593Smuzhiyun                    for line in f:
813*4882a593Smuzhiyun                        line = line.rstrip().strip('"\'')
814*4882a593Smuzhiyun                        linecount += 1
815*4882a593Smuzhiyun                        if line:
816*4882a593Smuzhiyun                            if linecount > 1:
817*4882a593Smuzhiyun                                version = None
818*4882a593Smuzhiyun                                break
819*4882a593Smuzhiyun                            else:
820*4882a593Smuzhiyun                                if validate_pv(line):
821*4882a593Smuzhiyun                                    version = line
822*4882a593Smuzhiyun                if version:
823*4882a593Smuzhiyun                    extravalues['PV'] = version
824*4882a593Smuzhiyun                    break
825*4882a593Smuzhiyun
826*4882a593Smuzhiyun
827*4882a593Smuzhiyunclass SpecFileRecipeHandler(RecipeHandler):
828*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
829*4882a593Smuzhiyun        if 'PV' in extravalues and 'PN' in extravalues:
830*4882a593Smuzhiyun            return
831*4882a593Smuzhiyun        filelist = RecipeHandler.checkfiles(srctree, ['*.spec'], recursive=True)
832*4882a593Smuzhiyun        valuemap = {'Name': 'PN',
833*4882a593Smuzhiyun                    'Version': 'PV',
834*4882a593Smuzhiyun                    'Summary': 'SUMMARY',
835*4882a593Smuzhiyun                    'Url': 'HOMEPAGE',
836*4882a593Smuzhiyun                    'License': 'LICENSE'}
837*4882a593Smuzhiyun        foundvalues = {}
838*4882a593Smuzhiyun        for fileitem in filelist:
839*4882a593Smuzhiyun            linecount = 0
840*4882a593Smuzhiyun            with open(fileitem, 'r', errors='surrogateescape') as f:
841*4882a593Smuzhiyun                for line in f:
842*4882a593Smuzhiyun                    for value, varname in valuemap.items():
843*4882a593Smuzhiyun                        if line.startswith(value + ':') and not varname in foundvalues:
844*4882a593Smuzhiyun                            foundvalues[varname] = line.split(':', 1)[1].strip()
845*4882a593Smuzhiyun                            break
846*4882a593Smuzhiyun                    if len(foundvalues) == len(valuemap):
847*4882a593Smuzhiyun                        break
848*4882a593Smuzhiyun        # Drop values containing unexpanded RPM macros
849*4882a593Smuzhiyun        for k in list(foundvalues.keys()):
850*4882a593Smuzhiyun            if '%' in foundvalues[k]:
851*4882a593Smuzhiyun                del foundvalues[k]
852*4882a593Smuzhiyun        if 'PV' in foundvalues:
853*4882a593Smuzhiyun            if not validate_pv(foundvalues['PV']):
854*4882a593Smuzhiyun                del foundvalues['PV']
855*4882a593Smuzhiyun        license = foundvalues.pop('LICENSE', None)
856*4882a593Smuzhiyun        if license:
857*4882a593Smuzhiyun            liccomment = '# NOTE: spec file indicates the license may be "%s"' % license
858*4882a593Smuzhiyun            for i, line in enumerate(lines_before):
859*4882a593Smuzhiyun                if line.startswith('LICENSE ='):
860*4882a593Smuzhiyun                    lines_before.insert(i, liccomment)
861*4882a593Smuzhiyun                    break
862*4882a593Smuzhiyun            else:
863*4882a593Smuzhiyun                lines_before.append(liccomment)
864*4882a593Smuzhiyun        extravalues.update(foundvalues)
865*4882a593Smuzhiyun
866*4882a593Smuzhiyundef register_recipe_handlers(handlers):
867*4882a593Smuzhiyun    # Set priorities with some gaps so that other plugins can insert
868*4882a593Smuzhiyun    # their own handlers (so avoid changing these numbers)
869*4882a593Smuzhiyun    handlers.append((CmakeRecipeHandler(), 50))
870*4882a593Smuzhiyun    handlers.append((AutotoolsRecipeHandler(), 40))
871*4882a593Smuzhiyun    handlers.append((SconsRecipeHandler(), 30))
872*4882a593Smuzhiyun    handlers.append((QmakeRecipeHandler(), 20))
873*4882a593Smuzhiyun    handlers.append((MakefileRecipeHandler(), 10))
874*4882a593Smuzhiyun    handlers.append((VersionFileRecipeHandler(), -1))
875*4882a593Smuzhiyun    handlers.append((SpecFileRecipeHandler(), -1))
876