xref: /OK3568_Linux_fs/yocto/scripts/lib/recipetool/create.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Recipe creation tool - create command plugin
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# Copyright (C) 2014-2017 Intel Corporation
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun
8*4882a593Smuzhiyunimport sys
9*4882a593Smuzhiyunimport os
10*4882a593Smuzhiyunimport argparse
11*4882a593Smuzhiyunimport glob
12*4882a593Smuzhiyunimport fnmatch
13*4882a593Smuzhiyunimport re
14*4882a593Smuzhiyunimport json
15*4882a593Smuzhiyunimport logging
16*4882a593Smuzhiyunimport scriptutils
17*4882a593Smuzhiyunfrom urllib.parse import urlparse, urldefrag, urlsplit
18*4882a593Smuzhiyunimport hashlib
19*4882a593Smuzhiyunimport bb.fetch2
20*4882a593Smuzhiyunlogger = logging.getLogger('recipetool')
21*4882a593Smuzhiyun
22*4882a593Smuzhiyuntinfoil = None
23*4882a593Smuzhiyunplugins = None
24*4882a593Smuzhiyun
25*4882a593Smuzhiyundef log_error_cond(message, debugonly):
26*4882a593Smuzhiyun    if debugonly:
27*4882a593Smuzhiyun        logger.debug(message)
28*4882a593Smuzhiyun    else:
29*4882a593Smuzhiyun        logger.error(message)
30*4882a593Smuzhiyun
31*4882a593Smuzhiyundef log_info_cond(message, debugonly):
32*4882a593Smuzhiyun    if debugonly:
33*4882a593Smuzhiyun        logger.debug(message)
34*4882a593Smuzhiyun    else:
35*4882a593Smuzhiyun        logger.info(message)
36*4882a593Smuzhiyun
37*4882a593Smuzhiyundef plugin_init(pluginlist):
38*4882a593Smuzhiyun    # Take a reference to the list so we can use it later
39*4882a593Smuzhiyun    global plugins
40*4882a593Smuzhiyun    plugins = pluginlist
41*4882a593Smuzhiyun
42*4882a593Smuzhiyundef tinfoil_init(instance):
43*4882a593Smuzhiyun    global tinfoil
44*4882a593Smuzhiyun    tinfoil = instance
45*4882a593Smuzhiyun
46*4882a593Smuzhiyunclass RecipeHandler(object):
47*4882a593Smuzhiyun    recipelibmap = {}
48*4882a593Smuzhiyun    recipeheadermap = {}
49*4882a593Smuzhiyun    recipecmakefilemap = {}
50*4882a593Smuzhiyun    recipebinmap = {}
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun    def __init__(self):
53*4882a593Smuzhiyun        self._devtool = False
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun    @staticmethod
56*4882a593Smuzhiyun    def load_libmap(d):
57*4882a593Smuzhiyun        '''Load library->recipe mapping'''
58*4882a593Smuzhiyun        import oe.package
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun        if RecipeHandler.recipelibmap:
61*4882a593Smuzhiyun            return
62*4882a593Smuzhiyun        # First build up library->package mapping
63*4882a593Smuzhiyun        d2 = bb.data.createCopy(d)
64*4882a593Smuzhiyun        d2.setVar("WORKDIR_PKGDATA", "${PKGDATA_DIR}")
65*4882a593Smuzhiyun        shlib_providers = oe.package.read_shlib_providers(d2)
66*4882a593Smuzhiyun        libdir = d.getVar('libdir')
67*4882a593Smuzhiyun        base_libdir = d.getVar('base_libdir')
68*4882a593Smuzhiyun        libpaths = list(set([base_libdir, libdir]))
69*4882a593Smuzhiyun        libname_re = re.compile(r'^lib(.+)\.so.*$')
70*4882a593Smuzhiyun        pkglibmap = {}
71*4882a593Smuzhiyun        for lib, item in shlib_providers.items():
72*4882a593Smuzhiyun            for path, pkg in item.items():
73*4882a593Smuzhiyun                if path in libpaths:
74*4882a593Smuzhiyun                    res = libname_re.match(lib)
75*4882a593Smuzhiyun                    if res:
76*4882a593Smuzhiyun                        libname = res.group(1)
77*4882a593Smuzhiyun                        if not libname in pkglibmap:
78*4882a593Smuzhiyun                            pkglibmap[libname] = pkg[0]
79*4882a593Smuzhiyun                    else:
80*4882a593Smuzhiyun                        logger.debug('unable to extract library name from %s' % lib)
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun        # Now turn it into a library->recipe mapping
83*4882a593Smuzhiyun        pkgdata_dir = d.getVar('PKGDATA_DIR')
84*4882a593Smuzhiyun        for libname, pkg in pkglibmap.items():
85*4882a593Smuzhiyun            try:
86*4882a593Smuzhiyun                with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
87*4882a593Smuzhiyun                    for line in f:
88*4882a593Smuzhiyun                        if line.startswith('PN:'):
89*4882a593Smuzhiyun                            RecipeHandler.recipelibmap[libname] = line.split(':', 1)[-1].strip()
90*4882a593Smuzhiyun                            break
91*4882a593Smuzhiyun            except IOError as ioe:
92*4882a593Smuzhiyun                if ioe.errno == 2:
93*4882a593Smuzhiyun                    logger.warning('unable to find a pkgdata file for package %s' % pkg)
94*4882a593Smuzhiyun                else:
95*4882a593Smuzhiyun                    raise
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun        # Some overrides - these should be mapped to the virtual
98*4882a593Smuzhiyun        RecipeHandler.recipelibmap['GL'] = 'virtual/libgl'
99*4882a593Smuzhiyun        RecipeHandler.recipelibmap['EGL'] = 'virtual/egl'
100*4882a593Smuzhiyun        RecipeHandler.recipelibmap['GLESv2'] = 'virtual/libgles2'
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun    @staticmethod
103*4882a593Smuzhiyun    def load_devel_filemap(d):
104*4882a593Smuzhiyun        '''Build up development file->recipe mapping'''
105*4882a593Smuzhiyun        if RecipeHandler.recipeheadermap:
106*4882a593Smuzhiyun            return
107*4882a593Smuzhiyun        pkgdata_dir = d.getVar('PKGDATA_DIR')
108*4882a593Smuzhiyun        includedir = d.getVar('includedir')
109*4882a593Smuzhiyun        cmakedir = os.path.join(d.getVar('libdir'), 'cmake')
110*4882a593Smuzhiyun        for pkg in glob.glob(os.path.join(pkgdata_dir, 'runtime', '*-dev')):
111*4882a593Smuzhiyun            with open(os.path.join(pkgdata_dir, 'runtime', pkg)) as f:
112*4882a593Smuzhiyun                pn = None
113*4882a593Smuzhiyun                headers = []
114*4882a593Smuzhiyun                cmakefiles = []
115*4882a593Smuzhiyun                for line in f:
116*4882a593Smuzhiyun                    if line.startswith('PN:'):
117*4882a593Smuzhiyun                        pn = line.split(':', 1)[-1].strip()
118*4882a593Smuzhiyun                    elif line.startswith('FILES_INFO:%s:' % pkg):
119*4882a593Smuzhiyun                        val = line.split(': ', 1)[1].strip()
120*4882a593Smuzhiyun                        dictval = json.loads(val)
121*4882a593Smuzhiyun                        for fullpth in sorted(dictval):
122*4882a593Smuzhiyun                            if fullpth.startswith(includedir) and fullpth.endswith('.h'):
123*4882a593Smuzhiyun                                headers.append(os.path.relpath(fullpth, includedir))
124*4882a593Smuzhiyun                            elif fullpth.startswith(cmakedir) and fullpth.endswith('.cmake'):
125*4882a593Smuzhiyun                                cmakefiles.append(os.path.relpath(fullpth, cmakedir))
126*4882a593Smuzhiyun                if pn and headers:
127*4882a593Smuzhiyun                    for header in headers:
128*4882a593Smuzhiyun                        RecipeHandler.recipeheadermap[header] = pn
129*4882a593Smuzhiyun                if pn and cmakefiles:
130*4882a593Smuzhiyun                    for fn in cmakefiles:
131*4882a593Smuzhiyun                        RecipeHandler.recipecmakefilemap[fn] = pn
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun    @staticmethod
134*4882a593Smuzhiyun    def load_binmap(d):
135*4882a593Smuzhiyun        '''Build up native binary->recipe mapping'''
136*4882a593Smuzhiyun        if RecipeHandler.recipebinmap:
137*4882a593Smuzhiyun            return
138*4882a593Smuzhiyun        sstate_manifests = d.getVar('SSTATE_MANIFESTS')
139*4882a593Smuzhiyun        staging_bindir_native = d.getVar('STAGING_BINDIR_NATIVE')
140*4882a593Smuzhiyun        build_arch = d.getVar('BUILD_ARCH')
141*4882a593Smuzhiyun        fileprefix = 'manifest-%s-' % build_arch
142*4882a593Smuzhiyun        for fn in glob.glob(os.path.join(sstate_manifests, '%s*-native.populate_sysroot' % fileprefix)):
143*4882a593Smuzhiyun            with open(fn, 'r') as f:
144*4882a593Smuzhiyun                pn = os.path.basename(fn).rsplit('.', 1)[0][len(fileprefix):]
145*4882a593Smuzhiyun                for line in f:
146*4882a593Smuzhiyun                    if line.startswith(staging_bindir_native):
147*4882a593Smuzhiyun                        prog = os.path.basename(line.rstrip())
148*4882a593Smuzhiyun                        RecipeHandler.recipebinmap[prog] = pn
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun    @staticmethod
151*4882a593Smuzhiyun    def checkfiles(path, speclist, recursive=False, excludedirs=None):
152*4882a593Smuzhiyun        results = []
153*4882a593Smuzhiyun        if recursive:
154*4882a593Smuzhiyun            for root, dirs, files in os.walk(path, topdown=True):
155*4882a593Smuzhiyun                if excludedirs:
156*4882a593Smuzhiyun                    dirs[:] = [d for d in dirs if d not in excludedirs]
157*4882a593Smuzhiyun                for fn in files:
158*4882a593Smuzhiyun                    for spec in speclist:
159*4882a593Smuzhiyun                        if fnmatch.fnmatch(fn, spec):
160*4882a593Smuzhiyun                            results.append(os.path.join(root, fn))
161*4882a593Smuzhiyun        else:
162*4882a593Smuzhiyun            for spec in speclist:
163*4882a593Smuzhiyun                results.extend(glob.glob(os.path.join(path, spec)))
164*4882a593Smuzhiyun        return results
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun    @staticmethod
167*4882a593Smuzhiyun    def handle_depends(libdeps, pcdeps, deps, outlines, values, d):
168*4882a593Smuzhiyun        if pcdeps:
169*4882a593Smuzhiyun            recipemap = read_pkgconfig_provides(d)
170*4882a593Smuzhiyun        if libdeps:
171*4882a593Smuzhiyun            RecipeHandler.load_libmap(d)
172*4882a593Smuzhiyun
173*4882a593Smuzhiyun        ignorelibs = ['socket']
174*4882a593Smuzhiyun        ignoredeps = ['gcc-runtime', 'glibc', 'uclibc', 'musl', 'tar-native', 'binutils-native', 'coreutils-native']
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun        unmappedpc = []
177*4882a593Smuzhiyun        pcdeps = list(set(pcdeps))
178*4882a593Smuzhiyun        for pcdep in pcdeps:
179*4882a593Smuzhiyun            if isinstance(pcdep, str):
180*4882a593Smuzhiyun                recipe = recipemap.get(pcdep, None)
181*4882a593Smuzhiyun                if recipe:
182*4882a593Smuzhiyun                    deps.append(recipe)
183*4882a593Smuzhiyun                else:
184*4882a593Smuzhiyun                    if not pcdep.startswith('$'):
185*4882a593Smuzhiyun                        unmappedpc.append(pcdep)
186*4882a593Smuzhiyun            else:
187*4882a593Smuzhiyun                for item in pcdep:
188*4882a593Smuzhiyun                    recipe = recipemap.get(pcdep, None)
189*4882a593Smuzhiyun                    if recipe:
190*4882a593Smuzhiyun                        deps.append(recipe)
191*4882a593Smuzhiyun                        break
192*4882a593Smuzhiyun                else:
193*4882a593Smuzhiyun                    unmappedpc.append('(%s)' % ' or '.join(pcdep))
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun        unmappedlibs = []
196*4882a593Smuzhiyun        for libdep in libdeps:
197*4882a593Smuzhiyun            if isinstance(libdep, tuple):
198*4882a593Smuzhiyun                lib, header = libdep
199*4882a593Smuzhiyun            else:
200*4882a593Smuzhiyun                lib = libdep
201*4882a593Smuzhiyun                header = None
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun            if lib in ignorelibs:
204*4882a593Smuzhiyun                logger.debug('Ignoring library dependency %s' % lib)
205*4882a593Smuzhiyun                continue
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun            recipe = RecipeHandler.recipelibmap.get(lib, None)
208*4882a593Smuzhiyun            if recipe:
209*4882a593Smuzhiyun                deps.append(recipe)
210*4882a593Smuzhiyun            elif recipe is None:
211*4882a593Smuzhiyun                if header:
212*4882a593Smuzhiyun                    RecipeHandler.load_devel_filemap(d)
213*4882a593Smuzhiyun                    recipe = RecipeHandler.recipeheadermap.get(header, None)
214*4882a593Smuzhiyun                    if recipe:
215*4882a593Smuzhiyun                        deps.append(recipe)
216*4882a593Smuzhiyun                    elif recipe is None:
217*4882a593Smuzhiyun                        unmappedlibs.append(lib)
218*4882a593Smuzhiyun                else:
219*4882a593Smuzhiyun                    unmappedlibs.append(lib)
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun        deps = set(deps).difference(set(ignoredeps))
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun        if unmappedpc:
224*4882a593Smuzhiyun            outlines.append('# NOTE: unable to map the following pkg-config dependencies: %s' % ' '.join(unmappedpc))
225*4882a593Smuzhiyun            outlines.append('#       (this is based on recipes that have previously been built and packaged)')
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun        if unmappedlibs:
228*4882a593Smuzhiyun            outlines.append('# NOTE: the following library dependencies are unknown, ignoring: %s' % ' '.join(list(set(unmappedlibs))))
229*4882a593Smuzhiyun            outlines.append('#       (this is based on recipes that have previously been built and packaged)')
230*4882a593Smuzhiyun
231*4882a593Smuzhiyun        if deps:
232*4882a593Smuzhiyun            values['DEPENDS'] = ' '.join(deps)
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun    @staticmethod
235*4882a593Smuzhiyun    def genfunction(outlines, funcname, content, python=False, forcespace=False):
236*4882a593Smuzhiyun        if python:
237*4882a593Smuzhiyun            prefix = 'python '
238*4882a593Smuzhiyun        else:
239*4882a593Smuzhiyun            prefix = ''
240*4882a593Smuzhiyun        outlines.append('%s%s () {' % (prefix, funcname))
241*4882a593Smuzhiyun        if python or forcespace:
242*4882a593Smuzhiyun            indent = '    '
243*4882a593Smuzhiyun        else:
244*4882a593Smuzhiyun            indent = '\t'
245*4882a593Smuzhiyun        addnoop = not python
246*4882a593Smuzhiyun        for line in content:
247*4882a593Smuzhiyun            outlines.append('%s%s' % (indent, line))
248*4882a593Smuzhiyun            if addnoop:
249*4882a593Smuzhiyun                strippedline = line.lstrip()
250*4882a593Smuzhiyun                if strippedline and not strippedline.startswith('#'):
251*4882a593Smuzhiyun                    addnoop = False
252*4882a593Smuzhiyun        if addnoop:
253*4882a593Smuzhiyun            # Without this there'll be a syntax error
254*4882a593Smuzhiyun            outlines.append('%s:' % indent)
255*4882a593Smuzhiyun        outlines.append('}')
256*4882a593Smuzhiyun        outlines.append('')
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
259*4882a593Smuzhiyun        return False
260*4882a593Smuzhiyun
261*4882a593Smuzhiyun
262*4882a593Smuzhiyundef validate_pv(pv):
263*4882a593Smuzhiyun    if not pv or '_version' in pv.lower() or pv[0] not in '0123456789':
264*4882a593Smuzhiyun        return False
265*4882a593Smuzhiyun    return True
266*4882a593Smuzhiyun
267*4882a593Smuzhiyundef determine_from_filename(srcfile):
268*4882a593Smuzhiyun    """Determine name and version from a filename"""
269*4882a593Smuzhiyun    if is_package(srcfile):
270*4882a593Smuzhiyun        # Force getting the value from the package metadata
271*4882a593Smuzhiyun        return None, None
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun    if '.tar.' in srcfile:
274*4882a593Smuzhiyun        namepart = srcfile.split('.tar.')[0]
275*4882a593Smuzhiyun    else:
276*4882a593Smuzhiyun        namepart = os.path.splitext(srcfile)[0]
277*4882a593Smuzhiyun    namepart = namepart.lower().replace('_', '-')
278*4882a593Smuzhiyun    if namepart.endswith('.src'):
279*4882a593Smuzhiyun        namepart = namepart[:-4]
280*4882a593Smuzhiyun    if namepart.endswith('.orig'):
281*4882a593Smuzhiyun        namepart = namepart[:-5]
282*4882a593Smuzhiyun    splitval = namepart.split('-')
283*4882a593Smuzhiyun    logger.debug('determine_from_filename: split name %s into: %s' % (srcfile, splitval))
284*4882a593Smuzhiyun
285*4882a593Smuzhiyun    ver_re = re.compile('^v?[0-9]')
286*4882a593Smuzhiyun
287*4882a593Smuzhiyun    pv = None
288*4882a593Smuzhiyun    pn = None
289*4882a593Smuzhiyun    if len(splitval) == 1:
290*4882a593Smuzhiyun        # Try to split the version out if there is no separator (or a .)
291*4882a593Smuzhiyun        res = re.match('^([^0-9]+)([0-9.]+.*)$', namepart)
292*4882a593Smuzhiyun        if res:
293*4882a593Smuzhiyun            if len(res.group(1)) > 1 and len(res.group(2)) > 1:
294*4882a593Smuzhiyun                pn = res.group(1).rstrip('.')
295*4882a593Smuzhiyun                pv = res.group(2)
296*4882a593Smuzhiyun        else:
297*4882a593Smuzhiyun            pn = namepart
298*4882a593Smuzhiyun    else:
299*4882a593Smuzhiyun        if splitval[-1] in ['source', 'src']:
300*4882a593Smuzhiyun            splitval.pop()
301*4882a593Smuzhiyun        if len(splitval) > 2 and re.match('^(alpha|beta|stable|release|rc[0-9]|pre[0-9]|p[0-9]|[0-9]{8})', splitval[-1]) and ver_re.match(splitval[-2]):
302*4882a593Smuzhiyun            pv = '-'.join(splitval[-2:])
303*4882a593Smuzhiyun            if pv.endswith('-release'):
304*4882a593Smuzhiyun                pv = pv[:-8]
305*4882a593Smuzhiyun            splitval = splitval[:-2]
306*4882a593Smuzhiyun        elif ver_re.match(splitval[-1]):
307*4882a593Smuzhiyun            pv = splitval.pop()
308*4882a593Smuzhiyun        pn = '-'.join(splitval)
309*4882a593Smuzhiyun        if pv and pv.startswith('v'):
310*4882a593Smuzhiyun            pv = pv[1:]
311*4882a593Smuzhiyun    logger.debug('determine_from_filename: name = "%s" version = "%s"' % (pn, pv))
312*4882a593Smuzhiyun    return (pn, pv)
313*4882a593Smuzhiyun
314*4882a593Smuzhiyundef determine_from_url(srcuri):
315*4882a593Smuzhiyun    """Determine name and version from a URL"""
316*4882a593Smuzhiyun    pn = None
317*4882a593Smuzhiyun    pv = None
318*4882a593Smuzhiyun    parseres = urlparse(srcuri.lower().split(';', 1)[0])
319*4882a593Smuzhiyun    if parseres.path:
320*4882a593Smuzhiyun        if 'github.com' in parseres.netloc:
321*4882a593Smuzhiyun            res = re.search(r'.*/(.*?)/archive/(.*)-final\.(tar|zip)', parseres.path)
322*4882a593Smuzhiyun            if res:
323*4882a593Smuzhiyun                pn = res.group(1).strip().replace('_', '-')
324*4882a593Smuzhiyun                pv = res.group(2).strip().replace('_', '.')
325*4882a593Smuzhiyun            else:
326*4882a593Smuzhiyun                res = re.search(r'.*/(.*?)/archive/v?(.*)\.(tar|zip)', parseres.path)
327*4882a593Smuzhiyun                if res:
328*4882a593Smuzhiyun                    pn = res.group(1).strip().replace('_', '-')
329*4882a593Smuzhiyun                    pv = res.group(2).strip().replace('_', '.')
330*4882a593Smuzhiyun        elif 'bitbucket.org' in parseres.netloc:
331*4882a593Smuzhiyun            res = re.search(r'.*/(.*?)/get/[a-zA-Z_-]*([0-9][0-9a-zA-Z_.]*)\.(tar|zip)', parseres.path)
332*4882a593Smuzhiyun            if res:
333*4882a593Smuzhiyun                pn = res.group(1).strip().replace('_', '-')
334*4882a593Smuzhiyun                pv = res.group(2).strip().replace('_', '.')
335*4882a593Smuzhiyun
336*4882a593Smuzhiyun        if not pn and not pv:
337*4882a593Smuzhiyun            if parseres.scheme not in ['git', 'gitsm', 'svn', 'hg']:
338*4882a593Smuzhiyun                srcfile = os.path.basename(parseres.path.rstrip('/'))
339*4882a593Smuzhiyun                pn, pv = determine_from_filename(srcfile)
340*4882a593Smuzhiyun            elif parseres.scheme in ['git', 'gitsm']:
341*4882a593Smuzhiyun                pn = os.path.basename(parseres.path.rstrip('/')).lower().replace('_', '-')
342*4882a593Smuzhiyun                if pn.endswith('.git'):
343*4882a593Smuzhiyun                    pn = pn[:-4]
344*4882a593Smuzhiyun
345*4882a593Smuzhiyun    logger.debug('Determined from source URL: name = "%s", version = "%s"' % (pn, pv))
346*4882a593Smuzhiyun    return (pn, pv)
347*4882a593Smuzhiyun
348*4882a593Smuzhiyundef supports_srcrev(uri):
349*4882a593Smuzhiyun    localdata = bb.data.createCopy(tinfoil.config_data)
350*4882a593Smuzhiyun    # This is a bit sad, but if you don't have this set there can be some
351*4882a593Smuzhiyun    # odd interactions with the urldata cache which lead to errors
352*4882a593Smuzhiyun    localdata.setVar('SRCREV', '${AUTOREV}')
353*4882a593Smuzhiyun    try:
354*4882a593Smuzhiyun        fetcher = bb.fetch2.Fetch([uri], localdata)
355*4882a593Smuzhiyun        urldata = fetcher.ud
356*4882a593Smuzhiyun        for u in urldata:
357*4882a593Smuzhiyun            if urldata[u].method.supports_srcrev():
358*4882a593Smuzhiyun                return True
359*4882a593Smuzhiyun    except bb.fetch2.FetchError as e:
360*4882a593Smuzhiyun        logger.debug('FetchError in supports_srcrev: %s' % str(e))
361*4882a593Smuzhiyun        # Fall back to basic check
362*4882a593Smuzhiyun        if uri.startswith(('git://', 'gitsm://')):
363*4882a593Smuzhiyun            return True
364*4882a593Smuzhiyun    return False
365*4882a593Smuzhiyun
366*4882a593Smuzhiyundef reformat_git_uri(uri):
367*4882a593Smuzhiyun    '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
368*4882a593Smuzhiyun    checkuri = uri.split(';', 1)[0]
369*4882a593Smuzhiyun    if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://git(hub|lab).com/[^/]+/[^/]+/?$', checkuri):
370*4882a593Smuzhiyun        # Appends scheme if the scheme is missing
371*4882a593Smuzhiyun        if not '://' in uri:
372*4882a593Smuzhiyun            uri = 'git://' + uri
373*4882a593Smuzhiyun        scheme, host, path, user, pswd, parms = bb.fetch2.decodeurl(uri)
374*4882a593Smuzhiyun        # Detection mechanism, this is required due to certain URL are formatter with ":" rather than "/"
375*4882a593Smuzhiyun        # which causes decodeurl to fail getting the right host and path
376*4882a593Smuzhiyun        if len(host.split(':')) > 1:
377*4882a593Smuzhiyun            splitslash = host.split(':')
378*4882a593Smuzhiyun            # Port number should not be split from host
379*4882a593Smuzhiyun            if not re.match('^[0-9]+$', splitslash[1]):
380*4882a593Smuzhiyun                host = splitslash[0]
381*4882a593Smuzhiyun                path = '/' + splitslash[1] + path
382*4882a593Smuzhiyun        #Algorithm:
383*4882a593Smuzhiyun        # if user is defined, append protocol=ssh or if a protocol is defined, then honor the user-defined protocol
384*4882a593Smuzhiyun        # if no user & password is defined, check for scheme type and append the protocol with the scheme type
385*4882a593Smuzhiyun        # finally if protocols or if the url is well-formed, do nothing and rejoin everything back to normal
386*4882a593Smuzhiyun        # Need to repackage the arguments for encodeurl, the format is: (scheme, host, path, user, password, OrderedDict([('key', 'value')]))
387*4882a593Smuzhiyun        if user:
388*4882a593Smuzhiyun            if not 'protocol' in parms:
389*4882a593Smuzhiyun                parms.update({('protocol', 'ssh')})
390*4882a593Smuzhiyun        elif (scheme == "http" or scheme == 'https' or scheme == 'ssh') and not ('protocol' in parms):
391*4882a593Smuzhiyun            parms.update({('protocol', scheme)})
392*4882a593Smuzhiyun        # Always append 'git://'
393*4882a593Smuzhiyun        fUrl = bb.fetch2.encodeurl(('git', host, path, user, pswd, parms))
394*4882a593Smuzhiyun        return fUrl
395*4882a593Smuzhiyun    else:
396*4882a593Smuzhiyun        return uri
397*4882a593Smuzhiyun
398*4882a593Smuzhiyundef is_package(url):
399*4882a593Smuzhiyun    '''Check if a URL points to a package'''
400*4882a593Smuzhiyun    checkurl = url.split(';', 1)[0]
401*4882a593Smuzhiyun    if checkurl.endswith(('.deb', '.ipk', '.rpm', '.srpm')):
402*4882a593Smuzhiyun        return True
403*4882a593Smuzhiyun    return False
404*4882a593Smuzhiyun
405*4882a593Smuzhiyundef create_recipe(args):
406*4882a593Smuzhiyun    import bb.process
407*4882a593Smuzhiyun    import tempfile
408*4882a593Smuzhiyun    import shutil
409*4882a593Smuzhiyun    import oe.recipeutils
410*4882a593Smuzhiyun
411*4882a593Smuzhiyun    pkgarch = ""
412*4882a593Smuzhiyun    if args.machine:
413*4882a593Smuzhiyun        pkgarch = "${MACHINE_ARCH}"
414*4882a593Smuzhiyun
415*4882a593Smuzhiyun    extravalues = {}
416*4882a593Smuzhiyun    checksums = {}
417*4882a593Smuzhiyun    tempsrc = ''
418*4882a593Smuzhiyun    source = args.source
419*4882a593Smuzhiyun    srcsubdir = ''
420*4882a593Smuzhiyun    srcrev = '${AUTOREV}'
421*4882a593Smuzhiyun    srcbranch = ''
422*4882a593Smuzhiyun    scheme = ''
423*4882a593Smuzhiyun    storeTagName = ''
424*4882a593Smuzhiyun    pv_srcpv = False
425*4882a593Smuzhiyun
426*4882a593Smuzhiyun    if os.path.isfile(source):
427*4882a593Smuzhiyun        source = 'file://%s' % os.path.abspath(source)
428*4882a593Smuzhiyun
429*4882a593Smuzhiyun    if scriptutils.is_src_url(source):
430*4882a593Smuzhiyun        # Warn about github archive URLs
431*4882a593Smuzhiyun        if re.match(r'https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source):
432*4882a593Smuzhiyun            logger.warning('github archive files are not guaranteed to be stable and may be re-generated over time. If the latter occurs, the checksums will likely change and the recipe will fail at do_fetch. It is recommended that you point to an actual commit or tag in the repository instead (using the repository URL in conjunction with the -S/--srcrev option).')
433*4882a593Smuzhiyun        # Fetch a URL
434*4882a593Smuzhiyun        fetchuri = reformat_git_uri(urldefrag(source)[0])
435*4882a593Smuzhiyun        if args.binary:
436*4882a593Smuzhiyun            # Assume the archive contains the directory structure verbatim
437*4882a593Smuzhiyun            # so we need to extract to a subdirectory
438*4882a593Smuzhiyun            fetchuri += ';subdir=${BPN}'
439*4882a593Smuzhiyun        srcuri = fetchuri
440*4882a593Smuzhiyun        rev_re = re.compile(';rev=([^;]+)')
441*4882a593Smuzhiyun        res = rev_re.search(srcuri)
442*4882a593Smuzhiyun        if res:
443*4882a593Smuzhiyun            if args.srcrev:
444*4882a593Smuzhiyun                logger.error('rev= parameter and -S/--srcrev option cannot both be specified - use one or the other')
445*4882a593Smuzhiyun                sys.exit(1)
446*4882a593Smuzhiyun            if args.autorev:
447*4882a593Smuzhiyun                logger.error('rev= parameter and -a/--autorev option cannot both be specified - use one or the other')
448*4882a593Smuzhiyun                sys.exit(1)
449*4882a593Smuzhiyun            srcrev = res.group(1)
450*4882a593Smuzhiyun            srcuri = rev_re.sub('', srcuri)
451*4882a593Smuzhiyun        elif args.srcrev:
452*4882a593Smuzhiyun            srcrev = args.srcrev
453*4882a593Smuzhiyun
454*4882a593Smuzhiyun        # Check whether users provides any branch info in fetchuri.
455*4882a593Smuzhiyun        # If true, we will skip all branch checking process to honor all user's input.
456*4882a593Smuzhiyun        scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(fetchuri)
457*4882a593Smuzhiyun        srcbranch = params.get('branch')
458*4882a593Smuzhiyun        if args.srcbranch:
459*4882a593Smuzhiyun            if srcbranch:
460*4882a593Smuzhiyun                logger.error('branch= parameter and -B/--srcbranch option cannot both be specified - use one or the other')
461*4882a593Smuzhiyun                sys.exit(1)
462*4882a593Smuzhiyun            srcbranch = args.srcbranch
463*4882a593Smuzhiyun            params['branch'] = srcbranch
464*4882a593Smuzhiyun        nobranch = params.get('nobranch')
465*4882a593Smuzhiyun        if nobranch and srcbranch:
466*4882a593Smuzhiyun            logger.error('nobranch= cannot be used if you specify a branch')
467*4882a593Smuzhiyun            sys.exit(1)
468*4882a593Smuzhiyun        tag = params.get('tag')
469*4882a593Smuzhiyun        if not srcbranch and not nobranch and srcrev != '${AUTOREV}':
470*4882a593Smuzhiyun            # Append nobranch=1 in the following conditions:
471*4882a593Smuzhiyun            # 1. User did not set 'branch=' in srcuri, and
472*4882a593Smuzhiyun            # 2. User did not set 'nobranch=1' in srcuri, and
473*4882a593Smuzhiyun            # 3. Source revision is not '${AUTOREV}'
474*4882a593Smuzhiyun            params['nobranch'] = '1'
475*4882a593Smuzhiyun        if tag:
476*4882a593Smuzhiyun            # Keep a copy of tag and append nobranch=1 then remove tag from URL.
477*4882a593Smuzhiyun            # Bitbake fetcher unable to fetch when {AUTOREV} and tag is set at the same time.
478*4882a593Smuzhiyun            storeTagName = params['tag']
479*4882a593Smuzhiyun            params['nobranch'] = '1'
480*4882a593Smuzhiyun            del params['tag']
481*4882a593Smuzhiyun        # Assume 'master' branch if not set
482*4882a593Smuzhiyun        if scheme in ['git', 'gitsm'] and 'branch' not in params and 'nobranch' not in params:
483*4882a593Smuzhiyun            params['branch'] = 'master'
484*4882a593Smuzhiyun        fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun        tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
487*4882a593Smuzhiyun        bb.utils.mkdirhier(tmpparent)
488*4882a593Smuzhiyun        tempsrc = tempfile.mkdtemp(prefix='recipetool-', dir=tmpparent)
489*4882a593Smuzhiyun        srctree = os.path.join(tempsrc, 'source')
490*4882a593Smuzhiyun
491*4882a593Smuzhiyun        try:
492*4882a593Smuzhiyun            checksums, ftmpdir = scriptutils.fetch_url(tinfoil, fetchuri, srcrev, srctree, logger, preserve_tmp=args.keep_temp)
493*4882a593Smuzhiyun        except scriptutils.FetchUrlFailure as e:
494*4882a593Smuzhiyun            logger.error(str(e))
495*4882a593Smuzhiyun            sys.exit(1)
496*4882a593Smuzhiyun
497*4882a593Smuzhiyun        if ftmpdir and args.keep_temp:
498*4882a593Smuzhiyun            logger.info('Fetch temp directory is %s' % ftmpdir)
499*4882a593Smuzhiyun
500*4882a593Smuzhiyun        dirlist = scriptutils.filter_src_subdirs(srctree)
501*4882a593Smuzhiyun        logger.debug('Directory listing (excluding filtered out):\n  %s' % '\n  '.join(dirlist))
502*4882a593Smuzhiyun        if len(dirlist) == 1:
503*4882a593Smuzhiyun            singleitem = os.path.join(srctree, dirlist[0])
504*4882a593Smuzhiyun            if os.path.isdir(singleitem):
505*4882a593Smuzhiyun                # We unpacked a single directory, so we should use that
506*4882a593Smuzhiyun                srcsubdir = dirlist[0]
507*4882a593Smuzhiyun                srctree = os.path.join(srctree, srcsubdir)
508*4882a593Smuzhiyun            else:
509*4882a593Smuzhiyun                check_single_file(dirlist[0], fetchuri)
510*4882a593Smuzhiyun        elif len(dirlist) == 0:
511*4882a593Smuzhiyun            if '/' in fetchuri:
512*4882a593Smuzhiyun                fn = os.path.join(tinfoil.config_data.getVar('DL_DIR'), fetchuri.split('/')[-1])
513*4882a593Smuzhiyun                if os.path.isfile(fn):
514*4882a593Smuzhiyun                    check_single_file(fn, fetchuri)
515*4882a593Smuzhiyun            # If we've got to here then there's no source so we might as well give up
516*4882a593Smuzhiyun            logger.error('URL %s resulted in an empty source tree' % fetchuri)
517*4882a593Smuzhiyun            sys.exit(1)
518*4882a593Smuzhiyun
519*4882a593Smuzhiyun        # We need this checking mechanism to improve the recipe created by recipetool and devtool
520*4882a593Smuzhiyun        # is able to parse and build by bitbake.
521*4882a593Smuzhiyun        # If there is no input for branch name, then check for branch name with SRCREV provided.
522*4882a593Smuzhiyun        if not srcbranch and not nobranch and srcrev and (srcrev != '${AUTOREV}') and scheme in ['git', 'gitsm']:
523*4882a593Smuzhiyun            try:
524*4882a593Smuzhiyun                cmd = 'git branch -r --contains'
525*4882a593Smuzhiyun                check_branch, check_branch_err = bb.process.run('%s %s' % (cmd, srcrev), cwd=srctree)
526*4882a593Smuzhiyun            except bb.process.ExecutionError as err:
527*4882a593Smuzhiyun                logger.error(str(err))
528*4882a593Smuzhiyun                sys.exit(1)
529*4882a593Smuzhiyun            get_branch = [x.strip() for x in check_branch.splitlines()]
530*4882a593Smuzhiyun            # Remove HEAD reference point and drop remote prefix
531*4882a593Smuzhiyun            get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
532*4882a593Smuzhiyun            if 'master' in get_branch:
533*4882a593Smuzhiyun                # Even with the case where get_branch has multiple objects, if 'master' is one
534*4882a593Smuzhiyun                # of them, we should default take from 'master'
535*4882a593Smuzhiyun                srcbranch = 'master'
536*4882a593Smuzhiyun            elif len(get_branch) == 1:
537*4882a593Smuzhiyun                # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
538*4882a593Smuzhiyun                srcbranch = get_branch[0]
539*4882a593Smuzhiyun            else:
540*4882a593Smuzhiyun                # If get_branch contains more than one objects, then display error and exit.
541*4882a593Smuzhiyun                mbrch = '\n  ' + '\n  '.join(get_branch)
542*4882a593Smuzhiyun                logger.error('Revision %s was found on multiple branches: %s\nPlease provide the correct branch with -B/--srcbranch' % (srcrev, mbrch))
543*4882a593Smuzhiyun                sys.exit(1)
544*4882a593Smuzhiyun
545*4882a593Smuzhiyun        # Since we might have a value in srcbranch, we need to
546*4882a593Smuzhiyun        # recontruct the srcuri to include 'branch' in params.
547*4882a593Smuzhiyun        scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
548*4882a593Smuzhiyun        if scheme in ['git', 'gitsm']:
549*4882a593Smuzhiyun            params['branch'] = srcbranch or 'master'
550*4882a593Smuzhiyun
551*4882a593Smuzhiyun        if storeTagName and scheme in ['git', 'gitsm']:
552*4882a593Smuzhiyun            # Check srcrev using tag and check validity of the tag
553*4882a593Smuzhiyun            cmd = ('git rev-parse --verify %s' % (storeTagName))
554*4882a593Smuzhiyun            try:
555*4882a593Smuzhiyun                check_tag, check_tag_err = bb.process.run('%s' % cmd, cwd=srctree)
556*4882a593Smuzhiyun                srcrev = check_tag.split()[0]
557*4882a593Smuzhiyun            except bb.process.ExecutionError as err:
558*4882a593Smuzhiyun                logger.error(str(err))
559*4882a593Smuzhiyun                logger.error("Possibly wrong tag name is provided")
560*4882a593Smuzhiyun                sys.exit(1)
561*4882a593Smuzhiyun            # Drop tag from srcuri as it will have conflicts with SRCREV during recipe parse.
562*4882a593Smuzhiyun            del params['tag']
563*4882a593Smuzhiyun        srcuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
564*4882a593Smuzhiyun
565*4882a593Smuzhiyun        if os.path.exists(os.path.join(srctree, '.gitmodules')) and srcuri.startswith('git://'):
566*4882a593Smuzhiyun            srcuri = 'gitsm://' + srcuri[6:]
567*4882a593Smuzhiyun            logger.info('Fetching submodules...')
568*4882a593Smuzhiyun            bb.process.run('git submodule update --init --recursive', cwd=srctree)
569*4882a593Smuzhiyun
570*4882a593Smuzhiyun        if is_package(fetchuri):
571*4882a593Smuzhiyun            localdata = bb.data.createCopy(tinfoil.config_data)
572*4882a593Smuzhiyun            pkgfile = bb.fetch2.localpath(fetchuri, localdata)
573*4882a593Smuzhiyun            if pkgfile:
574*4882a593Smuzhiyun                tmpfdir = tempfile.mkdtemp(prefix='recipetool-')
575*4882a593Smuzhiyun                try:
576*4882a593Smuzhiyun                    if pkgfile.endswith(('.deb', '.ipk')):
577*4882a593Smuzhiyun                        stdout, _ = bb.process.run('ar x %s' % pkgfile, cwd=tmpfdir)
578*4882a593Smuzhiyun                        stdout, _ = bb.process.run('tar xf control.tar.gz', cwd=tmpfdir)
579*4882a593Smuzhiyun                        values = convert_debian(tmpfdir)
580*4882a593Smuzhiyun                        extravalues.update(values)
581*4882a593Smuzhiyun                    elif pkgfile.endswith(('.rpm', '.srpm')):
582*4882a593Smuzhiyun                        stdout, _ = bb.process.run('rpm -qp --xml %s > pkginfo.xml' % pkgfile, cwd=tmpfdir)
583*4882a593Smuzhiyun                        values = convert_rpm_xml(os.path.join(tmpfdir, 'pkginfo.xml'))
584*4882a593Smuzhiyun                        extravalues.update(values)
585*4882a593Smuzhiyun                finally:
586*4882a593Smuzhiyun                    shutil.rmtree(tmpfdir)
587*4882a593Smuzhiyun    else:
588*4882a593Smuzhiyun        # Assume we're pointing to an existing source tree
589*4882a593Smuzhiyun        if args.extract_to:
590*4882a593Smuzhiyun            logger.error('--extract-to cannot be specified if source is a directory')
591*4882a593Smuzhiyun            sys.exit(1)
592*4882a593Smuzhiyun        if not os.path.isdir(source):
593*4882a593Smuzhiyun            logger.error('Invalid source directory %s' % source)
594*4882a593Smuzhiyun            sys.exit(1)
595*4882a593Smuzhiyun        srctree = source
596*4882a593Smuzhiyun        srcuri = ''
597*4882a593Smuzhiyun        if os.path.exists(os.path.join(srctree, '.git')):
598*4882a593Smuzhiyun            # Try to get upstream repo location from origin remote
599*4882a593Smuzhiyun            try:
600*4882a593Smuzhiyun                stdout, _ = bb.process.run('git remote -v', cwd=srctree, shell=True)
601*4882a593Smuzhiyun            except bb.process.ExecutionError as e:
602*4882a593Smuzhiyun                stdout = None
603*4882a593Smuzhiyun            if stdout:
604*4882a593Smuzhiyun                for line in stdout.splitlines():
605*4882a593Smuzhiyun                    splitline = line.split()
606*4882a593Smuzhiyun                    if len(splitline) > 1:
607*4882a593Smuzhiyun                        if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
608*4882a593Smuzhiyun                            srcuri = reformat_git_uri(splitline[1]) + ';branch=master'
609*4882a593Smuzhiyun                            srcsubdir = 'git'
610*4882a593Smuzhiyun                            break
611*4882a593Smuzhiyun
612*4882a593Smuzhiyun    if args.src_subdir:
613*4882a593Smuzhiyun        srcsubdir = os.path.join(srcsubdir, args.src_subdir)
614*4882a593Smuzhiyun        srctree_use = os.path.abspath(os.path.join(srctree, args.src_subdir))
615*4882a593Smuzhiyun    else:
616*4882a593Smuzhiyun        srctree_use = os.path.abspath(srctree)
617*4882a593Smuzhiyun
618*4882a593Smuzhiyun    if args.outfile and os.path.isdir(args.outfile):
619*4882a593Smuzhiyun        outfile = None
620*4882a593Smuzhiyun        outdir = args.outfile
621*4882a593Smuzhiyun    else:
622*4882a593Smuzhiyun        outfile = args.outfile
623*4882a593Smuzhiyun        outdir = None
624*4882a593Smuzhiyun    if outfile and outfile != '-':
625*4882a593Smuzhiyun        if os.path.exists(outfile):
626*4882a593Smuzhiyun            logger.error('Output file %s already exists' % outfile)
627*4882a593Smuzhiyun            sys.exit(1)
628*4882a593Smuzhiyun
629*4882a593Smuzhiyun    lines_before = []
630*4882a593Smuzhiyun    lines_after = []
631*4882a593Smuzhiyun
632*4882a593Smuzhiyun    lines_before.append('# Recipe created by %s' % os.path.basename(sys.argv[0]))
633*4882a593Smuzhiyun    lines_before.append('# This is the basis of a recipe and may need further editing in order to be fully functional.')
634*4882a593Smuzhiyun    lines_before.append('# (Feel free to remove these comments when editing.)')
635*4882a593Smuzhiyun    # We need a blank line here so that patch_recipe_lines can rewind before the LICENSE comments
636*4882a593Smuzhiyun    lines_before.append('')
637*4882a593Smuzhiyun
638*4882a593Smuzhiyun    # We'll come back and replace this later in handle_license_vars()
639*4882a593Smuzhiyun    lines_before.append('##LICENSE_PLACEHOLDER##')
640*4882a593Smuzhiyun
641*4882a593Smuzhiyun    handled = []
642*4882a593Smuzhiyun    classes = []
643*4882a593Smuzhiyun
644*4882a593Smuzhiyun    # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
645*4882a593Smuzhiyun    pn = None
646*4882a593Smuzhiyun    pv = None
647*4882a593Smuzhiyun    if outfile:
648*4882a593Smuzhiyun        recipefn = os.path.splitext(os.path.basename(outfile))[0]
649*4882a593Smuzhiyun        fnsplit = recipefn.split('_')
650*4882a593Smuzhiyun        if len(fnsplit) > 1:
651*4882a593Smuzhiyun            pn = fnsplit[0]
652*4882a593Smuzhiyun            pv = fnsplit[1]
653*4882a593Smuzhiyun        else:
654*4882a593Smuzhiyun            pn = recipefn
655*4882a593Smuzhiyun
656*4882a593Smuzhiyun    if args.version:
657*4882a593Smuzhiyun        pv = args.version
658*4882a593Smuzhiyun
659*4882a593Smuzhiyun    if args.name:
660*4882a593Smuzhiyun        pn = args.name
661*4882a593Smuzhiyun        if args.name.endswith('-native'):
662*4882a593Smuzhiyun            if args.also_native:
663*4882a593Smuzhiyun                logger.error('--also-native cannot be specified for a recipe named *-native (*-native denotes a recipe that is already only for native) - either remove the -native suffix from the name or drop --also-native')
664*4882a593Smuzhiyun                sys.exit(1)
665*4882a593Smuzhiyun            classes.append('native')
666*4882a593Smuzhiyun        elif args.name.startswith('nativesdk-'):
667*4882a593Smuzhiyun            if args.also_native:
668*4882a593Smuzhiyun                logger.error('--also-native cannot be specified for a recipe named nativesdk-* (nativesdk-* denotes a recipe that is already only for nativesdk)')
669*4882a593Smuzhiyun                sys.exit(1)
670*4882a593Smuzhiyun            classes.append('nativesdk')
671*4882a593Smuzhiyun
672*4882a593Smuzhiyun    if pv and pv not in 'git svn hg'.split():
673*4882a593Smuzhiyun        realpv = pv
674*4882a593Smuzhiyun    else:
675*4882a593Smuzhiyun        realpv = None
676*4882a593Smuzhiyun
677*4882a593Smuzhiyun    if not srcuri:
678*4882a593Smuzhiyun        lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
679*4882a593Smuzhiyun    lines_before.append('SRC_URI = "%s"' % srcuri)
680*4882a593Smuzhiyun    for key, value in sorted(checksums.items()):
681*4882a593Smuzhiyun        lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
682*4882a593Smuzhiyun    if srcuri and supports_srcrev(srcuri):
683*4882a593Smuzhiyun        lines_before.append('')
684*4882a593Smuzhiyun        lines_before.append('# Modify these as desired')
685*4882a593Smuzhiyun        # Note: we have code to replace realpv further down if it gets set to some other value
686*4882a593Smuzhiyun        scheme, _, _, _, _, _ = bb.fetch2.decodeurl(srcuri)
687*4882a593Smuzhiyun        if scheme in ['git', 'gitsm']:
688*4882a593Smuzhiyun            srcpvprefix = 'git'
689*4882a593Smuzhiyun        elif scheme == 'svn':
690*4882a593Smuzhiyun            srcpvprefix = 'svnr'
691*4882a593Smuzhiyun        else:
692*4882a593Smuzhiyun            srcpvprefix = scheme
693*4882a593Smuzhiyun        lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix))
694*4882a593Smuzhiyun        pv_srcpv = True
695*4882a593Smuzhiyun        if not args.autorev and srcrev == '${AUTOREV}':
696*4882a593Smuzhiyun            if os.path.exists(os.path.join(srctree, '.git')):
697*4882a593Smuzhiyun                (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
698*4882a593Smuzhiyun                srcrev = stdout.rstrip()
699*4882a593Smuzhiyun        lines_before.append('SRCREV = "%s"' % srcrev)
700*4882a593Smuzhiyun    if args.provides:
701*4882a593Smuzhiyun        lines_before.append('PROVIDES = "%s"' % args.provides)
702*4882a593Smuzhiyun    lines_before.append('')
703*4882a593Smuzhiyun
704*4882a593Smuzhiyun    if srcsubdir and not args.binary:
705*4882a593Smuzhiyun        # (for binary packages we explicitly specify subdir= when fetching to
706*4882a593Smuzhiyun        # match the default value of S, so we don't need to set it in that case)
707*4882a593Smuzhiyun        lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir)
708*4882a593Smuzhiyun        lines_before.append('')
709*4882a593Smuzhiyun
710*4882a593Smuzhiyun    if pkgarch:
711*4882a593Smuzhiyun        lines_after.append('PACKAGE_ARCH = "%s"' % pkgarch)
712*4882a593Smuzhiyun        lines_after.append('')
713*4882a593Smuzhiyun
714*4882a593Smuzhiyun    if args.binary:
715*4882a593Smuzhiyun        lines_after.append('INSANE_SKIP:${PN} += "already-stripped"')
716*4882a593Smuzhiyun        lines_after.append('')
717*4882a593Smuzhiyun
718*4882a593Smuzhiyun    if args.npm_dev:
719*4882a593Smuzhiyun        extravalues['NPM_INSTALL_DEV'] = 1
720*4882a593Smuzhiyun
721*4882a593Smuzhiyun    # Find all plugins that want to register handlers
722*4882a593Smuzhiyun    logger.debug('Loading recipe handlers')
723*4882a593Smuzhiyun    raw_handlers = []
724*4882a593Smuzhiyun    for plugin in plugins:
725*4882a593Smuzhiyun        if hasattr(plugin, 'register_recipe_handlers'):
726*4882a593Smuzhiyun            plugin.register_recipe_handlers(raw_handlers)
727*4882a593Smuzhiyun    # Sort handlers by priority
728*4882a593Smuzhiyun    handlers = []
729*4882a593Smuzhiyun    for i, handler in enumerate(raw_handlers):
730*4882a593Smuzhiyun        if isinstance(handler, tuple):
731*4882a593Smuzhiyun            handlers.append((handler[0], handler[1], i))
732*4882a593Smuzhiyun        else:
733*4882a593Smuzhiyun            handlers.append((handler, 0, i))
734*4882a593Smuzhiyun    handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
735*4882a593Smuzhiyun    for handler, priority, _ in handlers:
736*4882a593Smuzhiyun        logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
737*4882a593Smuzhiyun        setattr(handler, '_devtool', args.devtool)
738*4882a593Smuzhiyun    handlers = [item[0] for item in handlers]
739*4882a593Smuzhiyun
740*4882a593Smuzhiyun    # Apply the handlers
741*4882a593Smuzhiyun    if args.binary:
742*4882a593Smuzhiyun        classes.append('bin_package')
743*4882a593Smuzhiyun        handled.append('buildsystem')
744*4882a593Smuzhiyun
745*4882a593Smuzhiyun    for handler in handlers:
746*4882a593Smuzhiyun        handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
747*4882a593Smuzhiyun
748*4882a593Smuzhiyun    extrafiles = extravalues.pop('extrafiles', {})
749*4882a593Smuzhiyun    extra_pn = extravalues.pop('PN', None)
750*4882a593Smuzhiyun    extra_pv = extravalues.pop('PV', None)
751*4882a593Smuzhiyun
752*4882a593Smuzhiyun    if extra_pv and not realpv:
753*4882a593Smuzhiyun        realpv = extra_pv
754*4882a593Smuzhiyun        if not validate_pv(realpv):
755*4882a593Smuzhiyun            realpv = None
756*4882a593Smuzhiyun        else:
757*4882a593Smuzhiyun            realpv = realpv.lower().split()[0]
758*4882a593Smuzhiyun            if '_' in realpv:
759*4882a593Smuzhiyun                realpv = realpv.replace('_', '-')
760*4882a593Smuzhiyun    if extra_pn and not pn:
761*4882a593Smuzhiyun        pn = extra_pn
762*4882a593Smuzhiyun        if pn.startswith('GNU '):
763*4882a593Smuzhiyun            pn = pn[4:]
764*4882a593Smuzhiyun        if ' ' in pn:
765*4882a593Smuzhiyun            # Probably a descriptive identifier rather than a proper name
766*4882a593Smuzhiyun            pn = None
767*4882a593Smuzhiyun        else:
768*4882a593Smuzhiyun            pn = pn.lower()
769*4882a593Smuzhiyun            if '_' in pn:
770*4882a593Smuzhiyun                pn = pn.replace('_', '-')
771*4882a593Smuzhiyun
772*4882a593Smuzhiyun    if srcuri and not realpv or not pn:
773*4882a593Smuzhiyun        name_pn, name_pv = determine_from_url(srcuri)
774*4882a593Smuzhiyun        if name_pn and not pn:
775*4882a593Smuzhiyun            pn = name_pn
776*4882a593Smuzhiyun        if name_pv and not realpv:
777*4882a593Smuzhiyun            realpv = name_pv
778*4882a593Smuzhiyun
779*4882a593Smuzhiyun    licvalues = handle_license_vars(srctree_use, lines_before, handled, extravalues, tinfoil.config_data)
780*4882a593Smuzhiyun
781*4882a593Smuzhiyun    if not outfile:
782*4882a593Smuzhiyun        if not pn:
783*4882a593Smuzhiyun            log_error_cond('Unable to determine short program name from source tree - please specify name with -N/--name or output file name with -o/--outfile', args.devtool)
784*4882a593Smuzhiyun            # devtool looks for this specific exit code, so don't change it
785*4882a593Smuzhiyun            sys.exit(15)
786*4882a593Smuzhiyun        else:
787*4882a593Smuzhiyun            if srcuri and srcuri.startswith(('gitsm://', 'git://', 'hg://', 'svn://')):
788*4882a593Smuzhiyun                suffix = srcuri.split(':', 1)[0]
789*4882a593Smuzhiyun                if suffix == 'gitsm':
790*4882a593Smuzhiyun                    suffix = 'git'
791*4882a593Smuzhiyun                outfile = '%s_%s.bb' % (pn, suffix)
792*4882a593Smuzhiyun            elif realpv:
793*4882a593Smuzhiyun                outfile = '%s_%s.bb' % (pn, realpv)
794*4882a593Smuzhiyun            else:
795*4882a593Smuzhiyun                outfile = '%s.bb' % pn
796*4882a593Smuzhiyun            if outdir:
797*4882a593Smuzhiyun                outfile = os.path.join(outdir, outfile)
798*4882a593Smuzhiyun            # We need to check this again
799*4882a593Smuzhiyun            if os.path.exists(outfile):
800*4882a593Smuzhiyun                logger.error('Output file %s already exists' % outfile)
801*4882a593Smuzhiyun                sys.exit(1)
802*4882a593Smuzhiyun
803*4882a593Smuzhiyun    # Move any extra files the plugins created to a directory next to the recipe
804*4882a593Smuzhiyun    if extrafiles:
805*4882a593Smuzhiyun        if outfile == '-':
806*4882a593Smuzhiyun            extraoutdir = pn
807*4882a593Smuzhiyun        else:
808*4882a593Smuzhiyun            extraoutdir = os.path.join(os.path.dirname(outfile), pn)
809*4882a593Smuzhiyun        bb.utils.mkdirhier(extraoutdir)
810*4882a593Smuzhiyun        for destfn, extrafile in extrafiles.items():
811*4882a593Smuzhiyun            shutil.move(extrafile, os.path.join(extraoutdir, destfn))
812*4882a593Smuzhiyun
813*4882a593Smuzhiyun    lines = lines_before
814*4882a593Smuzhiyun    lines_before = []
815*4882a593Smuzhiyun    skipblank = True
816*4882a593Smuzhiyun    for line in lines:
817*4882a593Smuzhiyun        if skipblank:
818*4882a593Smuzhiyun            skipblank = False
819*4882a593Smuzhiyun            if not line:
820*4882a593Smuzhiyun                continue
821*4882a593Smuzhiyun        if line.startswith('S = '):
822*4882a593Smuzhiyun            if realpv and pv not in 'git svn hg'.split():
823*4882a593Smuzhiyun                line = line.replace(realpv, '${PV}')
824*4882a593Smuzhiyun            if pn:
825*4882a593Smuzhiyun                line = line.replace(pn, '${BPN}')
826*4882a593Smuzhiyun            if line == 'S = "${WORKDIR}/${BPN}-${PV}"':
827*4882a593Smuzhiyun                skipblank = True
828*4882a593Smuzhiyun                continue
829*4882a593Smuzhiyun        elif line.startswith('SRC_URI = '):
830*4882a593Smuzhiyun            if realpv and not pv_srcpv:
831*4882a593Smuzhiyun                line = line.replace(realpv, '${PV}')
832*4882a593Smuzhiyun        elif line.startswith('PV = '):
833*4882a593Smuzhiyun            if realpv:
834*4882a593Smuzhiyun                # Replace the first part of the PV value
835*4882a593Smuzhiyun                line = re.sub(r'"[^+]*\+', '"%s+' % realpv, line)
836*4882a593Smuzhiyun        lines_before.append(line)
837*4882a593Smuzhiyun
838*4882a593Smuzhiyun    if args.also_native:
839*4882a593Smuzhiyun        lines = lines_after
840*4882a593Smuzhiyun        lines_after = []
841*4882a593Smuzhiyun        bbclassextend = None
842*4882a593Smuzhiyun        for line in lines:
843*4882a593Smuzhiyun            if line.startswith('BBCLASSEXTEND ='):
844*4882a593Smuzhiyun                splitval = line.split('"')
845*4882a593Smuzhiyun                if len(splitval) > 1:
846*4882a593Smuzhiyun                    bbclassextend = splitval[1].split()
847*4882a593Smuzhiyun                    if not 'native' in bbclassextend:
848*4882a593Smuzhiyun                        bbclassextend.insert(0, 'native')
849*4882a593Smuzhiyun                line = 'BBCLASSEXTEND = "%s"' % ' '.join(bbclassextend)
850*4882a593Smuzhiyun            lines_after.append(line)
851*4882a593Smuzhiyun        if not bbclassextend:
852*4882a593Smuzhiyun            lines_after.append('BBCLASSEXTEND = "native"')
853*4882a593Smuzhiyun
854*4882a593Smuzhiyun    postinst = ("postinst", extravalues.pop('postinst', None))
855*4882a593Smuzhiyun    postrm = ("postrm", extravalues.pop('postrm', None))
856*4882a593Smuzhiyun    preinst = ("preinst", extravalues.pop('preinst', None))
857*4882a593Smuzhiyun    prerm = ("prerm", extravalues.pop('prerm', None))
858*4882a593Smuzhiyun    funcs = [postinst, postrm, preinst, prerm]
859*4882a593Smuzhiyun    for func in funcs:
860*4882a593Smuzhiyun        if func[1]:
861*4882a593Smuzhiyun            RecipeHandler.genfunction(lines_after, 'pkg_%s_${PN}' % func[0], func[1])
862*4882a593Smuzhiyun
863*4882a593Smuzhiyun    outlines = []
864*4882a593Smuzhiyun    outlines.extend(lines_before)
865*4882a593Smuzhiyun    if classes:
866*4882a593Smuzhiyun        if outlines[-1] and not outlines[-1].startswith('#'):
867*4882a593Smuzhiyun            outlines.append('')
868*4882a593Smuzhiyun        outlines.append('inherit %s' % ' '.join(classes))
869*4882a593Smuzhiyun        outlines.append('')
870*4882a593Smuzhiyun    outlines.extend(lines_after)
871*4882a593Smuzhiyun
872*4882a593Smuzhiyun    if extravalues:
873*4882a593Smuzhiyun        _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False)
874*4882a593Smuzhiyun
875*4882a593Smuzhiyun    if args.extract_to:
876*4882a593Smuzhiyun        scriptutils.git_convert_standalone_clone(srctree)
877*4882a593Smuzhiyun        if os.path.isdir(args.extract_to):
878*4882a593Smuzhiyun            # If the directory exists we'll move the temp dir into it instead of
879*4882a593Smuzhiyun            # its contents - of course, we could try to always move its contents
880*4882a593Smuzhiyun            # but that is a pain if there are symlinks; the simplest solution is
881*4882a593Smuzhiyun            # to just remove it first
882*4882a593Smuzhiyun            os.rmdir(args.extract_to)
883*4882a593Smuzhiyun        shutil.move(srctree, args.extract_to)
884*4882a593Smuzhiyun        if tempsrc == srctree:
885*4882a593Smuzhiyun            tempsrc = None
886*4882a593Smuzhiyun        log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
887*4882a593Smuzhiyun
888*4882a593Smuzhiyun    if outfile == '-':
889*4882a593Smuzhiyun        sys.stdout.write('\n'.join(outlines) + '\n')
890*4882a593Smuzhiyun    else:
891*4882a593Smuzhiyun        with open(outfile, 'w') as f:
892*4882a593Smuzhiyun            lastline = None
893*4882a593Smuzhiyun            for line in outlines:
894*4882a593Smuzhiyun                if not lastline and not line:
895*4882a593Smuzhiyun                    # Skip extra blank lines
896*4882a593Smuzhiyun                    continue
897*4882a593Smuzhiyun                f.write('%s\n' % line)
898*4882a593Smuzhiyun                lastline = line
899*4882a593Smuzhiyun        log_info_cond('Recipe %s has been created; further editing may be required to make it fully functional' % outfile, args.devtool)
900*4882a593Smuzhiyun
901*4882a593Smuzhiyun    if tempsrc:
902*4882a593Smuzhiyun        if args.keep_temp:
903*4882a593Smuzhiyun            logger.info('Preserving temporary directory %s' % tempsrc)
904*4882a593Smuzhiyun        else:
905*4882a593Smuzhiyun            shutil.rmtree(tempsrc)
906*4882a593Smuzhiyun
907*4882a593Smuzhiyun    return 0
908*4882a593Smuzhiyun
909*4882a593Smuzhiyundef check_single_file(fn, fetchuri):
910*4882a593Smuzhiyun    """Determine if a single downloaded file is something we can't handle"""
911*4882a593Smuzhiyun    with open(fn, 'r', errors='surrogateescape') as f:
912*4882a593Smuzhiyun        if '<html' in f.read(100).lower():
913*4882a593Smuzhiyun            logger.error('Fetching "%s" returned a single HTML page - check the URL is correct and functional' % fetchuri)
914*4882a593Smuzhiyun            sys.exit(1)
915*4882a593Smuzhiyun
916*4882a593Smuzhiyundef split_value(value):
917*4882a593Smuzhiyun    if isinstance(value, str):
918*4882a593Smuzhiyun        return value.split()
919*4882a593Smuzhiyun    else:
920*4882a593Smuzhiyun        return value
921*4882a593Smuzhiyun
922*4882a593Smuzhiyundef fixup_license(value):
923*4882a593Smuzhiyun    # Ensure licenses with OR starts and ends with brackets
924*4882a593Smuzhiyun    if '|' in value:
925*4882a593Smuzhiyun        return '(' + value + ')'
926*4882a593Smuzhiyun    return value
927*4882a593Smuzhiyun
928*4882a593Smuzhiyundef tidy_licenses(value):
929*4882a593Smuzhiyun    """Flat, split and sort licenses"""
930*4882a593Smuzhiyun    from oe.license import flattened_licenses
931*4882a593Smuzhiyun    def _choose(a, b):
932*4882a593Smuzhiyun        str_a, str_b  = sorted((" & ".join(a), " & ".join(b)), key=str.casefold)
933*4882a593Smuzhiyun        return ["(%s | %s)" % (str_a, str_b)]
934*4882a593Smuzhiyun    if not isinstance(value, str):
935*4882a593Smuzhiyun        value = " & ".join(value)
936*4882a593Smuzhiyun    return sorted(list(set(flattened_licenses(value, _choose))), key=str.casefold)
937*4882a593Smuzhiyun
938*4882a593Smuzhiyundef handle_license_vars(srctree, lines_before, handled, extravalues, d):
939*4882a593Smuzhiyun    lichandled = [x for x in handled if x[0] == 'license']
940*4882a593Smuzhiyun    if lichandled:
941*4882a593Smuzhiyun        # Someone else has already handled the license vars, just return their value
942*4882a593Smuzhiyun        return lichandled[0][1]
943*4882a593Smuzhiyun
944*4882a593Smuzhiyun    licvalues = guess_license(srctree, d)
945*4882a593Smuzhiyun    licenses = []
946*4882a593Smuzhiyun    lic_files_chksum = []
947*4882a593Smuzhiyun    lic_unknown = []
948*4882a593Smuzhiyun    lines = []
949*4882a593Smuzhiyun    if licvalues:
950*4882a593Smuzhiyun        for licvalue in licvalues:
951*4882a593Smuzhiyun            license = licvalue[0]
952*4882a593Smuzhiyun            lics = tidy_licenses(fixup_license(license))
953*4882a593Smuzhiyun            lics = [lic for lic in lics if lic not in licenses]
954*4882a593Smuzhiyun            if len(lics):
955*4882a593Smuzhiyun                licenses.extend(lics)
956*4882a593Smuzhiyun            lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
957*4882a593Smuzhiyun            if license == 'Unknown':
958*4882a593Smuzhiyun                lic_unknown.append(licvalue[1])
959*4882a593Smuzhiyun        if lic_unknown:
960*4882a593Smuzhiyun            lines.append('#')
961*4882a593Smuzhiyun            lines.append('# The following license files were not able to be identified and are')
962*4882a593Smuzhiyun            lines.append('# represented as "Unknown" below, you will need to check them yourself:')
963*4882a593Smuzhiyun            for licfile in lic_unknown:
964*4882a593Smuzhiyun                lines.append('#   %s' % licfile)
965*4882a593Smuzhiyun
966*4882a593Smuzhiyun    extra_license = tidy_licenses(extravalues.pop('LICENSE', ''))
967*4882a593Smuzhiyun    if extra_license:
968*4882a593Smuzhiyun        if licenses == ['Unknown']:
969*4882a593Smuzhiyun            licenses = extra_license
970*4882a593Smuzhiyun        else:
971*4882a593Smuzhiyun            for item in extra_license:
972*4882a593Smuzhiyun                if item not in licenses:
973*4882a593Smuzhiyun                    licenses.append(item)
974*4882a593Smuzhiyun    extra_lic_files_chksum = split_value(extravalues.pop('LIC_FILES_CHKSUM', []))
975*4882a593Smuzhiyun    for item in extra_lic_files_chksum:
976*4882a593Smuzhiyun        if item not in lic_files_chksum:
977*4882a593Smuzhiyun            lic_files_chksum.append(item)
978*4882a593Smuzhiyun
979*4882a593Smuzhiyun    if lic_files_chksum:
980*4882a593Smuzhiyun        # We are going to set the vars, so prepend the standard disclaimer
981*4882a593Smuzhiyun        lines.insert(0, '# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is')
982*4882a593Smuzhiyun        lines.insert(1, '# your responsibility to verify that the values are complete and correct.')
983*4882a593Smuzhiyun    else:
984*4882a593Smuzhiyun        # Without LIC_FILES_CHKSUM we set LICENSE = "CLOSED" to allow the
985*4882a593Smuzhiyun        # user to get started easily
986*4882a593Smuzhiyun        lines.append('# Unable to find any files that looked like license statements. Check the accompanying')
987*4882a593Smuzhiyun        lines.append('# documentation and source headers and set LICENSE and LIC_FILES_CHKSUM accordingly.')
988*4882a593Smuzhiyun        lines.append('#')
989*4882a593Smuzhiyun        lines.append('# NOTE: LICENSE is being set to "CLOSED" to allow you to at least start building - if')
990*4882a593Smuzhiyun        lines.append('# this is not accurate with respect to the licensing of the software being built (it')
991*4882a593Smuzhiyun        lines.append('# will not be in most cases) you must specify the correct value before using this')
992*4882a593Smuzhiyun        lines.append('# recipe for anything other than initial testing/development!')
993*4882a593Smuzhiyun        licenses = ['CLOSED']
994*4882a593Smuzhiyun
995*4882a593Smuzhiyun    if extra_license and sorted(licenses) != sorted(extra_license):
996*4882a593Smuzhiyun        lines.append('# NOTE: Original package / source metadata indicates license is: %s' % ' & '.join(extra_license))
997*4882a593Smuzhiyun
998*4882a593Smuzhiyun    if len(licenses) > 1:
999*4882a593Smuzhiyun        lines.append('#')
1000*4882a593Smuzhiyun        lines.append('# NOTE: multiple licenses have been detected; they have been separated with &')
1001*4882a593Smuzhiyun        lines.append('# in the LICENSE value for now since it is a reasonable assumption that all')
1002*4882a593Smuzhiyun        lines.append('# of the licenses apply. If instead there is a choice between the multiple')
1003*4882a593Smuzhiyun        lines.append('# licenses then you should change the value to separate the licenses with |')
1004*4882a593Smuzhiyun        lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
1005*4882a593Smuzhiyun        lines.append('# to determine which situation is applicable.')
1006*4882a593Smuzhiyun
1007*4882a593Smuzhiyun    lines.append('LICENSE = "%s"' % ' & '.join(sorted(licenses, key=str.casefold)))
1008*4882a593Smuzhiyun    lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n                    '.join(lic_files_chksum))
1009*4882a593Smuzhiyun    lines.append('')
1010*4882a593Smuzhiyun
1011*4882a593Smuzhiyun    # Replace the placeholder so we get the values in the right place in the recipe file
1012*4882a593Smuzhiyun    try:
1013*4882a593Smuzhiyun        pos = lines_before.index('##LICENSE_PLACEHOLDER##')
1014*4882a593Smuzhiyun    except ValueError:
1015*4882a593Smuzhiyun        pos = -1
1016*4882a593Smuzhiyun    if pos == -1:
1017*4882a593Smuzhiyun        lines_before.extend(lines)
1018*4882a593Smuzhiyun    else:
1019*4882a593Smuzhiyun        lines_before[pos:pos+1] = lines
1020*4882a593Smuzhiyun
1021*4882a593Smuzhiyun    handled.append(('license', licvalues))
1022*4882a593Smuzhiyun    return licvalues
1023*4882a593Smuzhiyun
1024*4882a593Smuzhiyundef get_license_md5sums(d, static_only=False, linenumbers=False):
1025*4882a593Smuzhiyun    import bb.utils
1026*4882a593Smuzhiyun    import csv
1027*4882a593Smuzhiyun    md5sums = {}
1028*4882a593Smuzhiyun    if not static_only and not linenumbers:
1029*4882a593Smuzhiyun        # Gather md5sums of license files in common license dir
1030*4882a593Smuzhiyun        commonlicdir = d.getVar('COMMON_LICENSE_DIR')
1031*4882a593Smuzhiyun        for fn in os.listdir(commonlicdir):
1032*4882a593Smuzhiyun            md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1033*4882a593Smuzhiyun            md5sums[md5value] = fn
1034*4882a593Smuzhiyun
1035*4882a593Smuzhiyun    # The following were extracted from common values in various recipes
1036*4882a593Smuzhiyun    # (double checking the license against the license file itself, not just
1037*4882a593Smuzhiyun    # the LICENSE value in the recipe)
1038*4882a593Smuzhiyun
1039*4882a593Smuzhiyun    # Read license md5sums from csv file
1040*4882a593Smuzhiyun    scripts_path = os.path.dirname(os.path.realpath(__file__))
1041*4882a593Smuzhiyun    for path in (d.getVar('BBPATH').split(':')
1042*4882a593Smuzhiyun                + [os.path.join(scripts_path, '..', '..')]):
1043*4882a593Smuzhiyun        csv_path = os.path.join(path, 'lib', 'recipetool', 'licenses.csv')
1044*4882a593Smuzhiyun        if os.path.isfile(csv_path):
1045*4882a593Smuzhiyun            with open(csv_path, newline='') as csv_file:
1046*4882a593Smuzhiyun                fieldnames = ['md5sum', 'license', 'beginline', 'endline', 'md5']
1047*4882a593Smuzhiyun                reader = csv.DictReader(csv_file, delimiter=',', fieldnames=fieldnames)
1048*4882a593Smuzhiyun                for row in reader:
1049*4882a593Smuzhiyun                    if linenumbers:
1050*4882a593Smuzhiyun                        md5sums[row['md5sum']] = (
1051*4882a593Smuzhiyun                            row['license'], row['beginline'], row['endline'], row['md5'])
1052*4882a593Smuzhiyun                    else:
1053*4882a593Smuzhiyun                        md5sums[row['md5sum']] = row['license']
1054*4882a593Smuzhiyun
1055*4882a593Smuzhiyun    return md5sums
1056*4882a593Smuzhiyun
1057*4882a593Smuzhiyundef crunch_license(licfile):
1058*4882a593Smuzhiyun    '''
1059*4882a593Smuzhiyun    Remove non-material text from a license file and then check
1060*4882a593Smuzhiyun    its md5sum against a known list. This works well for licenses
1061*4882a593Smuzhiyun    which contain a copyright statement, but is also a useful way
1062*4882a593Smuzhiyun    to handle people's insistence upon reformatting the license text
1063*4882a593Smuzhiyun    slightly (with no material difference to the text of the
1064*4882a593Smuzhiyun    license).
1065*4882a593Smuzhiyun    '''
1066*4882a593Smuzhiyun
1067*4882a593Smuzhiyun    import oe.utils
1068*4882a593Smuzhiyun
1069*4882a593Smuzhiyun    # Note: these are carefully constructed!
1070*4882a593Smuzhiyun    license_title_re = re.compile(r'^#*\(? *(This is )?([Tt]he )?.{0,15} ?[Ll]icen[sc]e( \(.{1,10}\))?\)?[:\.]? ?#*$')
1071*4882a593Smuzhiyun    license_statement_re = re.compile(r'^((This (project|software)|.{1,10}) is( free software)? (released|licen[sc]ed)|(Released|Licen[cs]ed)) under the .{1,10} [Ll]icen[sc]e:?$')
1072*4882a593Smuzhiyun    copyright_re = re.compile('^ *[#\*]* *(Modified work |MIT LICENSED )?Copyright ?(\([cC]\))? .*$')
1073*4882a593Smuzhiyun    disclaimer_re = re.compile('^ *\*? ?All [Rr]ights [Rr]eserved\.$')
1074*4882a593Smuzhiyun    email_re = re.compile('^.*<[\w\.-]*@[\w\.\-]*>$')
1075*4882a593Smuzhiyun    header_re = re.compile('^(\/\**!?)? ?[\-=\*]* ?(\*\/)?$')
1076*4882a593Smuzhiyun    tag_re = re.compile('^ *@?\(?([Ll]icense|MIT)\)?$')
1077*4882a593Smuzhiyun    url_re = re.compile('^ *[#\*]* *https?:\/\/[\w\.\/\-]+$')
1078*4882a593Smuzhiyun
1079*4882a593Smuzhiyun    crunched_md5sums = {}
1080*4882a593Smuzhiyun
1081*4882a593Smuzhiyun    # common licenses
1082*4882a593Smuzhiyun    crunched_md5sums['89f3bf322f30a1dcfe952e09945842f0'] = 'Apache-2.0'
1083*4882a593Smuzhiyun    crunched_md5sums['13b6fe3075f8f42f2270a748965bf3a1'] = '0BSD'
1084*4882a593Smuzhiyun    crunched_md5sums['ba87a7d7c20719c8df4b8beed9b78c43'] = 'BSD-2-Clause'
1085*4882a593Smuzhiyun    crunched_md5sums['7f8892c03b72de419c27be4ebfa253f8'] = 'BSD-3-Clause'
1086*4882a593Smuzhiyun    crunched_md5sums['21128c0790b23a8a9f9e260d5f6b3619'] = 'BSL-1.0'
1087*4882a593Smuzhiyun    crunched_md5sums['975742a59ae1b8abdea63a97121f49f4'] = 'EDL-1.0'
1088*4882a593Smuzhiyun    crunched_md5sums['5322cee4433d84fb3aafc9e253116447'] = 'EPL-1.0'
1089*4882a593Smuzhiyun    crunched_md5sums['6922352e87de080f42419bed93063754'] = 'EPL-2.0'
1090*4882a593Smuzhiyun    crunched_md5sums['793475baa22295cae1d3d4046a3a0ceb'] = 'GPL-2.0-only'
1091*4882a593Smuzhiyun    crunched_md5sums['ff9047f969b02c20f0559470df5cb433'] = 'GPL-2.0-or-later'
1092*4882a593Smuzhiyun    crunched_md5sums['ea6de5453fcadf534df246e6cdafadcd'] = 'GPL-3.0-only'
1093*4882a593Smuzhiyun    crunched_md5sums['b419257d4d153a6fde92ddf96acf5b67'] = 'GPL-3.0-or-later'
1094*4882a593Smuzhiyun    crunched_md5sums['228737f4c49d3ee75b8fb3706b090b84'] = 'ISC'
1095*4882a593Smuzhiyun    crunched_md5sums['c6a782e826ca4e85bf7f8b89435a677d'] = 'LGPL-2.0-only'
1096*4882a593Smuzhiyun    crunched_md5sums['32d8f758a066752f0db09bd7624b8090'] = 'LGPL-2.0-or-later'
1097*4882a593Smuzhiyun    crunched_md5sums['4820937eb198b4f84c52217ed230be33'] = 'LGPL-2.1-only'
1098*4882a593Smuzhiyun    crunched_md5sums['db13fe9f3a13af7adab2dc7a76f9e44a'] = 'LGPL-2.1-or-later'
1099*4882a593Smuzhiyun    crunched_md5sums['d7a0f2e4e0950e837ac3eabf5bd1d246'] = 'LGPL-3.0-only'
1100*4882a593Smuzhiyun    crunched_md5sums['abbf328e2b434f9153351f06b9f79d02'] = 'LGPL-3.0-or-later'
1101*4882a593Smuzhiyun    crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
1102*4882a593Smuzhiyun    crunched_md5sums['b218b0e94290b9b818c4be67c8e1cc82'] = 'MIT-0'
1103*4882a593Smuzhiyun    crunched_md5sums['ddc18131d6748374f0f35a621c245b49'] = 'Unlicense'
1104*4882a593Smuzhiyun    crunched_md5sums['51f9570ff32571fc0a443102285c5e33'] = 'WTFPL'
1105*4882a593Smuzhiyun
1106*4882a593Smuzhiyun    # The following two were gleaned from the "forever" npm package
1107*4882a593Smuzhiyun    crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
1108*4882a593Smuzhiyun    # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
1109*4882a593Smuzhiyun    crunched_md5sums['50fab24ce589d69af8964fdbfe414c60'] = 'BSD-2-Clause'
1110*4882a593Smuzhiyun    # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
1111*4882a593Smuzhiyun    crunched_md5sums['88a4355858a1433fea99fae34a44da88'] = 'GPL-2.0-only'
1112*4882a593Smuzhiyun    # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
1113*4882a593Smuzhiyun    crunched_md5sums['063b5c3ebb5f3aa4c85a2ed18a31fbe7'] = 'GPL-2.0-only'
1114*4882a593Smuzhiyun    # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
1115*4882a593Smuzhiyun    crunched_md5sums['7f5202f4d44ed15dcd4915f5210417d8'] = 'LGPL-2.1-only'
1116*4882a593Smuzhiyun    # unixODBC-2.3.4 COPYING
1117*4882a593Smuzhiyun    crunched_md5sums['3debde09238a8c8e1f6a847e1ec9055b'] = 'LGPL-2.1-only'
1118*4882a593Smuzhiyun    # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
1119*4882a593Smuzhiyun    crunched_md5sums['f90c613c51aa35da4d79dd55fc724ceb'] = 'LGPL-3.0-only'
1120*4882a593Smuzhiyun    # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1121*4882a593Smuzhiyun    crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
1122*4882a593Smuzhiyun
1123*4882a593Smuzhiyun    # https://raw.githubusercontent.com/jquery/esprima/3.1.3/LICENSE.BSD
1124*4882a593Smuzhiyun    crunched_md5sums['80fa7b56a28e8c902e6af194003220a5'] = 'BSD-2-Clause'
1125*4882a593Smuzhiyun    # https://raw.githubusercontent.com/npm/npm-install-checks/master/LICENSE
1126*4882a593Smuzhiyun    crunched_md5sums['e659f77bfd9002659e112d0d3d59b2c1'] = 'BSD-2-Clause'
1127*4882a593Smuzhiyun    # https://raw.githubusercontent.com/silverwind/default-gateway/4.2.0/LICENSE
1128*4882a593Smuzhiyun    crunched_md5sums['4c641f2d995c47f5cb08bdb4b5b6ea05'] = 'BSD-2-Clause'
1129*4882a593Smuzhiyun    # https://raw.githubusercontent.com/tad-lispy/node-damerau-levenshtein/v1.0.5/LICENSE
1130*4882a593Smuzhiyun    crunched_md5sums['2b8c039b2b9a25f0feb4410c4542d346'] = 'BSD-2-Clause'
1131*4882a593Smuzhiyun    # https://raw.githubusercontent.com/terser/terser/v3.17.0/LICENSE
1132*4882a593Smuzhiyun    crunched_md5sums['8bd23871802951c9ad63855151204c2c'] = 'BSD-2-Clause'
1133*4882a593Smuzhiyun    # https://raw.githubusercontent.com/alexei/sprintf.js/1.0.3/LICENSE
1134*4882a593Smuzhiyun    crunched_md5sums['008c22318c8ea65928bf730ddd0273e3'] = 'BSD-3-Clause'
1135*4882a593Smuzhiyun    # https://raw.githubusercontent.com/Caligatio/jsSHA/v3.2.0/LICENSE
1136*4882a593Smuzhiyun    crunched_md5sums['0e46634a01bfef056892949acaea85b1'] = 'BSD-3-Clause'
1137*4882a593Smuzhiyun    # https://raw.githubusercontent.com/d3/d3-path/v1.0.9/LICENSE
1138*4882a593Smuzhiyun    crunched_md5sums['b5f72aef53d3b2b432702c30b0215666'] = 'BSD-3-Clause'
1139*4882a593Smuzhiyun    # https://raw.githubusercontent.com/feross/ieee754/v1.1.13/LICENSE
1140*4882a593Smuzhiyun    crunched_md5sums['a39327c997c20da0937955192d86232d'] = 'BSD-3-Clause'
1141*4882a593Smuzhiyun    # https://raw.githubusercontent.com/joyent/node-extsprintf/v1.3.0/LICENSE
1142*4882a593Smuzhiyun    crunched_md5sums['721f23a96ff4161ca3a5f071bbe18108'] = 'MIT'
1143*4882a593Smuzhiyun    # https://raw.githubusercontent.com/pvorb/clone/v0.2.0/LICENSE
1144*4882a593Smuzhiyun    crunched_md5sums['b376d29a53c9573006b9970709231431'] = 'MIT'
1145*4882a593Smuzhiyun    # https://raw.githubusercontent.com/andris9/encoding/v0.1.12/LICENSE
1146*4882a593Smuzhiyun    crunched_md5sums['85d8a977ee9d7c5ab4ac03c9b95431c4'] = 'MIT-0'
1147*4882a593Smuzhiyun    # https://raw.githubusercontent.com/faye/websocket-driver-node/0.7.3/LICENSE.md
1148*4882a593Smuzhiyun    crunched_md5sums['b66384e7137e41a9b1904ef4d39703b6'] = 'Apache-2.0'
1149*4882a593Smuzhiyun    # https://raw.githubusercontent.com/less/less.js/v4.1.1/LICENSE
1150*4882a593Smuzhiyun    crunched_md5sums['b27575459e02221ccef97ec0bfd457ae'] = 'Apache-2.0'
1151*4882a593Smuzhiyun    # https://raw.githubusercontent.com/microsoft/TypeScript/v3.5.3/LICENSE.txt
1152*4882a593Smuzhiyun    crunched_md5sums['a54a1a6a39e7f9dbb4a23a42f5c7fd1c'] = 'Apache-2.0'
1153*4882a593Smuzhiyun    # https://raw.githubusercontent.com/request/request/v2.87.0/LICENSE
1154*4882a593Smuzhiyun    crunched_md5sums['1034431802e57486b393d00c5d262b8a'] = 'Apache-2.0'
1155*4882a593Smuzhiyun    # https://raw.githubusercontent.com/dchest/tweetnacl-js/v0.14.5/LICENSE
1156*4882a593Smuzhiyun    crunched_md5sums['75605e6bdd564791ab698fca65c94a4f'] = 'Unlicense'
1157*4882a593Smuzhiyun    # https://raw.githubusercontent.com/stackgl/gl-mat3/v2.0.0/LICENSE.md
1158*4882a593Smuzhiyun    crunched_md5sums['75512892d6f59dddb6d1c7e191957e9c'] = 'Zlib'
1159*4882a593Smuzhiyun
1160*4882a593Smuzhiyun    lictext = []
1161*4882a593Smuzhiyun    with open(licfile, 'r', errors='surrogateescape') as f:
1162*4882a593Smuzhiyun        for line in f:
1163*4882a593Smuzhiyun            # Drop opening statements
1164*4882a593Smuzhiyun            if copyright_re.match(line):
1165*4882a593Smuzhiyun                continue
1166*4882a593Smuzhiyun            elif disclaimer_re.match(line):
1167*4882a593Smuzhiyun                continue
1168*4882a593Smuzhiyun            elif email_re.match(line):
1169*4882a593Smuzhiyun                continue
1170*4882a593Smuzhiyun            elif header_re.match(line):
1171*4882a593Smuzhiyun                continue
1172*4882a593Smuzhiyun            elif tag_re.match(line):
1173*4882a593Smuzhiyun                continue
1174*4882a593Smuzhiyun            elif url_re.match(line):
1175*4882a593Smuzhiyun                continue
1176*4882a593Smuzhiyun            elif license_title_re.match(line):
1177*4882a593Smuzhiyun                continue
1178*4882a593Smuzhiyun            elif license_statement_re.match(line):
1179*4882a593Smuzhiyun                continue
1180*4882a593Smuzhiyun            # Strip comment symbols
1181*4882a593Smuzhiyun            line = line.replace('*', '') \
1182*4882a593Smuzhiyun                       .replace('#', '')
1183*4882a593Smuzhiyun            # Unify spelling
1184*4882a593Smuzhiyun            line = line.replace('sub-license', 'sublicense')
1185*4882a593Smuzhiyun            # Squash spaces
1186*4882a593Smuzhiyun            line = oe.utils.squashspaces(line.strip())
1187*4882a593Smuzhiyun            # Replace smart quotes, double quotes and backticks with single quotes
1188*4882a593Smuzhiyun            line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
1189*4882a593Smuzhiyun            # Unify brackets
1190*4882a593Smuzhiyun            line = line.replace("{", "[").replace("}", "]")
1191*4882a593Smuzhiyun            if line:
1192*4882a593Smuzhiyun                lictext.append(line)
1193*4882a593Smuzhiyun
1194*4882a593Smuzhiyun    m = hashlib.md5()
1195*4882a593Smuzhiyun    try:
1196*4882a593Smuzhiyun        m.update(' '.join(lictext).encode('utf-8'))
1197*4882a593Smuzhiyun        md5val = m.hexdigest()
1198*4882a593Smuzhiyun    except UnicodeEncodeError:
1199*4882a593Smuzhiyun        md5val = None
1200*4882a593Smuzhiyun        lictext = ''
1201*4882a593Smuzhiyun    license = crunched_md5sums.get(md5val, None)
1202*4882a593Smuzhiyun    return license, md5val, lictext
1203*4882a593Smuzhiyun
1204*4882a593Smuzhiyundef guess_license(srctree, d):
1205*4882a593Smuzhiyun    import bb
1206*4882a593Smuzhiyun    md5sums = get_license_md5sums(d)
1207*4882a593Smuzhiyun
1208*4882a593Smuzhiyun    licenses = []
1209*4882a593Smuzhiyun    licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
1210*4882a593Smuzhiyun    skip_extensions = (".html", ".js", ".json", ".svg", ".ts")
1211*4882a593Smuzhiyun    licfiles = []
1212*4882a593Smuzhiyun    for root, dirs, files in os.walk(srctree):
1213*4882a593Smuzhiyun        for fn in files:
1214*4882a593Smuzhiyun            if fn.endswith(skip_extensions):
1215*4882a593Smuzhiyun                continue
1216*4882a593Smuzhiyun            for spec in licspecs:
1217*4882a593Smuzhiyun                if fnmatch.fnmatch(fn, spec):
1218*4882a593Smuzhiyun                    fullpath = os.path.join(root, fn)
1219*4882a593Smuzhiyun                    if not fullpath in licfiles:
1220*4882a593Smuzhiyun                        licfiles.append(fullpath)
1221*4882a593Smuzhiyun    for licfile in sorted(licfiles):
1222*4882a593Smuzhiyun        md5value = bb.utils.md5_file(licfile)
1223*4882a593Smuzhiyun        license = md5sums.get(md5value, None)
1224*4882a593Smuzhiyun        if not license:
1225*4882a593Smuzhiyun            license, crunched_md5, lictext = crunch_license(licfile)
1226*4882a593Smuzhiyun            if lictext and not license:
1227*4882a593Smuzhiyun                license = 'Unknown'
1228*4882a593Smuzhiyun                logger.info("Please add the following line for '%s' to a 'lib/recipetool/licenses.csv' " \
1229*4882a593Smuzhiyun                    "and replace `Unknown` with the license:\n" \
1230*4882a593Smuzhiyun                    "%s,Unknown" % (os.path.relpath(licfile, srctree), md5value))
1231*4882a593Smuzhiyun        if license:
1232*4882a593Smuzhiyun            licenses.append((license, os.path.relpath(licfile, srctree), md5value))
1233*4882a593Smuzhiyun
1234*4882a593Smuzhiyun    # FIXME should we grab at least one source file with a license header and add that too?
1235*4882a593Smuzhiyun
1236*4882a593Smuzhiyun    return licenses
1237*4882a593Smuzhiyun
1238*4882a593Smuzhiyundef split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=[], pn='${PN}'):
1239*4882a593Smuzhiyun    """
1240*4882a593Smuzhiyun    Given a list of (license, path, md5sum) as returned by guess_license(),
1241*4882a593Smuzhiyun    a dict of package name to path mappings, write out a set of
1242*4882a593Smuzhiyun    package-specific LICENSE values.
1243*4882a593Smuzhiyun    """
1244*4882a593Smuzhiyun    pkglicenses = {pn: []}
1245*4882a593Smuzhiyun    for license, licpath, _ in licvalues:
1246*4882a593Smuzhiyun        license = fixup_license(license)
1247*4882a593Smuzhiyun        for pkgname, pkgpath in packages.items():
1248*4882a593Smuzhiyun            if licpath.startswith(pkgpath + '/'):
1249*4882a593Smuzhiyun                if pkgname in pkglicenses:
1250*4882a593Smuzhiyun                    pkglicenses[pkgname].append(license)
1251*4882a593Smuzhiyun                else:
1252*4882a593Smuzhiyun                    pkglicenses[pkgname] = [license]
1253*4882a593Smuzhiyun                break
1254*4882a593Smuzhiyun        else:
1255*4882a593Smuzhiyun            # Accumulate on the main package
1256*4882a593Smuzhiyun            pkglicenses[pn].append(license)
1257*4882a593Smuzhiyun    outlicenses = {}
1258*4882a593Smuzhiyun    for pkgname in packages:
1259*4882a593Smuzhiyun        # Assume AND operator between license files
1260*4882a593Smuzhiyun        license = ' & '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
1261*4882a593Smuzhiyun        if license == 'Unknown' and pkgname in fallback_licenses:
1262*4882a593Smuzhiyun            license = fallback_licenses[pkgname]
1263*4882a593Smuzhiyun        licenses = tidy_licenses(license)
1264*4882a593Smuzhiyun        license = ' & '.join(licenses)
1265*4882a593Smuzhiyun        outlines.append('LICENSE:%s = "%s"' % (pkgname, license))
1266*4882a593Smuzhiyun        outlicenses[pkgname] = licenses
1267*4882a593Smuzhiyun    return outlicenses
1268*4882a593Smuzhiyun
1269*4882a593Smuzhiyundef read_pkgconfig_provides(d):
1270*4882a593Smuzhiyun    pkgdatadir = d.getVar('PKGDATA_DIR')
1271*4882a593Smuzhiyun    pkgmap = {}
1272*4882a593Smuzhiyun    for fn in glob.glob(os.path.join(pkgdatadir, 'shlibs2', '*.pclist')):
1273*4882a593Smuzhiyun        with open(fn, 'r') as f:
1274*4882a593Smuzhiyun            for line in f:
1275*4882a593Smuzhiyun                pkgmap[os.path.basename(line.rstrip())] = os.path.splitext(os.path.basename(fn))[0]
1276*4882a593Smuzhiyun    recipemap = {}
1277*4882a593Smuzhiyun    for pc, pkg in pkgmap.items():
1278*4882a593Smuzhiyun        pkgdatafile = os.path.join(pkgdatadir, 'runtime', pkg)
1279*4882a593Smuzhiyun        if os.path.exists(pkgdatafile):
1280*4882a593Smuzhiyun            with open(pkgdatafile, 'r') as f:
1281*4882a593Smuzhiyun                for line in f:
1282*4882a593Smuzhiyun                    if line.startswith('PN: '):
1283*4882a593Smuzhiyun                        recipemap[pc] = line.split(':', 1)[1].strip()
1284*4882a593Smuzhiyun    return recipemap
1285*4882a593Smuzhiyun
1286*4882a593Smuzhiyundef convert_debian(debpath):
1287*4882a593Smuzhiyun    value_map = {'Package': 'PN',
1288*4882a593Smuzhiyun                 'Version': 'PV',
1289*4882a593Smuzhiyun                 'Section': 'SECTION',
1290*4882a593Smuzhiyun                 'License': 'LICENSE',
1291*4882a593Smuzhiyun                 'Homepage': 'HOMEPAGE'}
1292*4882a593Smuzhiyun
1293*4882a593Smuzhiyun    # FIXME extend this mapping - perhaps use distro_alias.inc?
1294*4882a593Smuzhiyun    depmap = {'libz-dev': 'zlib'}
1295*4882a593Smuzhiyun
1296*4882a593Smuzhiyun    values = {}
1297*4882a593Smuzhiyun    depends = []
1298*4882a593Smuzhiyun    with open(os.path.join(debpath, 'control'), 'r', errors='surrogateescape') as f:
1299*4882a593Smuzhiyun        indesc = False
1300*4882a593Smuzhiyun        for line in f:
1301*4882a593Smuzhiyun            if indesc:
1302*4882a593Smuzhiyun                if line.startswith(' '):
1303*4882a593Smuzhiyun                    if line.startswith(' This package contains'):
1304*4882a593Smuzhiyun                        indesc = False
1305*4882a593Smuzhiyun                    else:
1306*4882a593Smuzhiyun                        if 'DESCRIPTION' in values:
1307*4882a593Smuzhiyun                            values['DESCRIPTION'] += ' ' + line.strip()
1308*4882a593Smuzhiyun                        else:
1309*4882a593Smuzhiyun                            values['DESCRIPTION'] = line.strip()
1310*4882a593Smuzhiyun                else:
1311*4882a593Smuzhiyun                    indesc = False
1312*4882a593Smuzhiyun            if not indesc:
1313*4882a593Smuzhiyun                splitline = line.split(':', 1)
1314*4882a593Smuzhiyun                if len(splitline) < 2:
1315*4882a593Smuzhiyun                    continue
1316*4882a593Smuzhiyun                key = splitline[0]
1317*4882a593Smuzhiyun                value = splitline[1].strip()
1318*4882a593Smuzhiyun                if key == 'Build-Depends':
1319*4882a593Smuzhiyun                    for dep in value.split(','):
1320*4882a593Smuzhiyun                        dep = dep.split()[0]
1321*4882a593Smuzhiyun                        mapped = depmap.get(dep, '')
1322*4882a593Smuzhiyun                        if mapped:
1323*4882a593Smuzhiyun                            depends.append(mapped)
1324*4882a593Smuzhiyun                elif key == 'Description':
1325*4882a593Smuzhiyun                    values['SUMMARY'] = value
1326*4882a593Smuzhiyun                    indesc = True
1327*4882a593Smuzhiyun                else:
1328*4882a593Smuzhiyun                    varname = value_map.get(key, None)
1329*4882a593Smuzhiyun                    if varname:
1330*4882a593Smuzhiyun                        values[varname] = value
1331*4882a593Smuzhiyun    postinst = os.path.join(debpath, 'postinst')
1332*4882a593Smuzhiyun    postrm = os.path.join(debpath, 'postrm')
1333*4882a593Smuzhiyun    preinst = os.path.join(debpath, 'preinst')
1334*4882a593Smuzhiyun    prerm = os.path.join(debpath, 'prerm')
1335*4882a593Smuzhiyun    sfiles = [postinst, postrm, preinst, prerm]
1336*4882a593Smuzhiyun    for sfile in sfiles:
1337*4882a593Smuzhiyun        if os.path.isfile(sfile):
1338*4882a593Smuzhiyun            logger.info("Converting %s file to recipe function..." %
1339*4882a593Smuzhiyun                    os.path.basename(sfile).upper())
1340*4882a593Smuzhiyun            content = []
1341*4882a593Smuzhiyun            with open(sfile) as f:
1342*4882a593Smuzhiyun                for line in f:
1343*4882a593Smuzhiyun                    if "#!/" in line:
1344*4882a593Smuzhiyun                        continue
1345*4882a593Smuzhiyun                    line = line.rstrip("\n")
1346*4882a593Smuzhiyun                    if line.strip():
1347*4882a593Smuzhiyun                        content.append(line)
1348*4882a593Smuzhiyun                if content:
1349*4882a593Smuzhiyun                    values[os.path.basename(f.name)] = content
1350*4882a593Smuzhiyun
1351*4882a593Smuzhiyun    #if depends:
1352*4882a593Smuzhiyun    #    values['DEPENDS'] = ' '.join(depends)
1353*4882a593Smuzhiyun
1354*4882a593Smuzhiyun    return values
1355*4882a593Smuzhiyun
1356*4882a593Smuzhiyundef convert_rpm_xml(xmlfile):
1357*4882a593Smuzhiyun    '''Converts the output from rpm -qp --xml to a set of variable values'''
1358*4882a593Smuzhiyun    import xml.etree.ElementTree as ElementTree
1359*4882a593Smuzhiyun    rpmtag_map = {'Name': 'PN',
1360*4882a593Smuzhiyun                  'Version': 'PV',
1361*4882a593Smuzhiyun                  'Summary': 'SUMMARY',
1362*4882a593Smuzhiyun                  'Description': 'DESCRIPTION',
1363*4882a593Smuzhiyun                  'License': 'LICENSE',
1364*4882a593Smuzhiyun                  'Url': 'HOMEPAGE'}
1365*4882a593Smuzhiyun
1366*4882a593Smuzhiyun    values = {}
1367*4882a593Smuzhiyun    tree = ElementTree.parse(xmlfile)
1368*4882a593Smuzhiyun    root = tree.getroot()
1369*4882a593Smuzhiyun    for child in root:
1370*4882a593Smuzhiyun        if child.tag == 'rpmTag':
1371*4882a593Smuzhiyun            name = child.attrib.get('name', None)
1372*4882a593Smuzhiyun            if name:
1373*4882a593Smuzhiyun                varname = rpmtag_map.get(name, None)
1374*4882a593Smuzhiyun                if varname:
1375*4882a593Smuzhiyun                    values[varname] = child[0].text
1376*4882a593Smuzhiyun    return values
1377*4882a593Smuzhiyun
1378*4882a593Smuzhiyun
1379*4882a593Smuzhiyundef register_commands(subparsers):
1380*4882a593Smuzhiyun    parser_create = subparsers.add_parser('create',
1381*4882a593Smuzhiyun                                          help='Create a new recipe',
1382*4882a593Smuzhiyun                                          description='Creates a new recipe from a source tree')
1383*4882a593Smuzhiyun    parser_create.add_argument('source', help='Path or URL to source')
1384*4882a593Smuzhiyun    parser_create.add_argument('-o', '--outfile', help='Specify filename for recipe to create')
1385*4882a593Smuzhiyun    parser_create.add_argument('-p', '--provides', help='Specify an alias for the item provided by the recipe')
1386*4882a593Smuzhiyun    parser_create.add_argument('-m', '--machine', help='Make recipe machine-specific as opposed to architecture-specific', action='store_true')
1387*4882a593Smuzhiyun    parser_create.add_argument('-x', '--extract-to', metavar='EXTRACTPATH', help='Assuming source is a URL, fetch it and extract it to the directory specified as %(metavar)s')
1388*4882a593Smuzhiyun    parser_create.add_argument('-N', '--name', help='Name to use within recipe (PN)')
1389*4882a593Smuzhiyun    parser_create.add_argument('-V', '--version', help='Version to use within recipe (PV)')
1390*4882a593Smuzhiyun    parser_create.add_argument('-b', '--binary', help='Treat the source tree as something that should be installed verbatim (no compilation, same directory structure)', action='store_true')
1391*4882a593Smuzhiyun    parser_create.add_argument('--also-native', help='Also add native variant (i.e. support building recipe for the build host as well as the target machine)', action='store_true')
1392*4882a593Smuzhiyun    parser_create.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
1393*4882a593Smuzhiyun    group = parser_create.add_mutually_exclusive_group()
1394*4882a593Smuzhiyun    group.add_argument('-a', '--autorev', help='When fetching from a git repository, set SRCREV in the recipe to a floating revision instead of fixed', action="store_true")
1395*4882a593Smuzhiyun    group.add_argument('-S', '--srcrev', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
1396*4882a593Smuzhiyun    parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)')
1397*4882a593Smuzhiyun    parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
1398*4882a593Smuzhiyun    parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies')
1399*4882a593Smuzhiyun    parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
1400*4882a593Smuzhiyun    parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1401*4882a593Smuzhiyun    parser_create.set_defaults(func=create_recipe)
1402*4882a593Smuzhiyun
1403