|
1 | 1 | # |
2 | | -# Copyright (c) 2020 nexB Inc. and others. All rights reserved. |
3 | | -# http://nexb.com and https://github.com/nexB/scancode-toolkit/ |
4 | | -# The ScanCode software is licensed under the Apache License version 2.0. |
5 | | -# Data generated with ScanCode require an acknowledgment. |
| 2 | +# Copyright (c) nexB Inc. and others. All rights reserved. |
6 | 3 | # ScanCode is a trademark of nexB Inc. |
| 4 | +# SPDX-License-Identifier: Apache-2.0 |
| 5 | +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. |
| 6 | +# See https://github.com/nexB/scancode-toolkit for support or download. |
| 7 | +# See https://aboutcode.org for more information about nexB OSS projects. |
7 | 8 | # |
8 | | -# You may not use this software except in compliance with the License. |
9 | | -# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 |
10 | | -# Unless required by applicable law or agreed to in writing, software distributed |
11 | | -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR |
12 | | -# CONDITIONS OF ANY KIND, either express or implied. See the License for the |
13 | | -# specific language governing permissions and limitations under the License. |
14 | | -# |
15 | | -# When you publish or redistribute any data created with ScanCode or any ScanCode |
16 | | -# derivative work, you must accompany this data with the following acknowledgment: |
17 | | -# |
18 | | -# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES |
19 | | -# OR CONDITIONS OF ANY KIND, either express or implied. No content created from |
20 | | -# ScanCode should be considered or used as legal advice. Consult an Attorney |
21 | | -# for any legal advice. |
22 | | -# ScanCode is a free software code scanning tool from nexB Inc. and others. |
23 | | -# Visit https://github.com/nexB/scancode-toolkit/ for support and download. |
24 | | - |
25 | | -from __future__ import absolute_import |
26 | | -from __future__ import print_function |
27 | | -from __future__ import unicode_literals |
28 | 9 |
|
29 | 10 | import logging |
| 11 | +import re |
30 | 12 |
|
31 | | -import attr |
| 13 | +from oelint_parser.cls_stash import Stash |
| 14 | +from packageurl import PackageURL |
32 | 15 |
|
33 | | -from commoncode import filetype |
34 | 16 | from packagedcode import models |
35 | 17 |
|
36 | | -from oelint_parser.cls_stash import Stash |
37 | | -from oelint_parser.cls_item import Variable |
38 | | - |
39 | | -TRACE = False |
| 18 | +TRACE = True |
40 | 19 |
|
41 | 20 | logger = logging.getLogger(__name__) |
42 | 21 |
|
|
45 | 24 | logging.basicConfig(stream=sys.stdout) |
46 | 25 | logger.setLevel(logging.DEBUG) |
47 | 26 |
|
48 | | -@attr.s() |
49 | | -class BitbakePackage(models.Package): |
50 | | - metafiles = ('*.bb',) |
51 | | - default_type = 'bitbake' |
52 | 27 |
|
53 | | - @classmethod |
54 | | - def recognize(cls, location): |
55 | | - yield parse(location) |
| 28 | +class BitbakeBbManifestHandler(models.DatafileHandler): |
| 29 | + datasource_id = 'bitbake_bb_recipe' |
| 30 | + # note that there are .bbappend, .bbclass and bitbake.conf files. |
| 31 | + path_patterns = ('*.bb',) |
| 32 | + default_package_type = 'bitbake' |
| 33 | + description = 'BitBake bb recipe manifest' |
| 34 | + documentation_url = 'https://docs.yoctoproject.org/bitbake/bitbake-user-manual/bitbake-user-manual-metadata.html' |
56 | 35 |
|
57 | 36 | @classmethod |
58 | | - def get_package_root(cls, manifest_resource, codebase): |
59 | | - return manifest_resource.parent(codebase) |
| 37 | + def parse(cls, location): |
| 38 | + |
| 39 | + oestash = Stash(quiet=True) |
| 40 | + |
| 41 | + # add any bitbake like-file |
| 42 | + # TODO: may be we should handle the bbclass and bbappend here? |
| 43 | + oestash.AddFile(location) |
| 44 | + |
| 45 | + # Resolves proper cross file dependencies |
| 46 | + oestash.Finalize() |
| 47 | + |
| 48 | + # collect all variables of interest. |
| 49 | + # TODO: we should not get list values. Instead plain strings |
| 50 | + data = { |
| 51 | + k: ' '.join(v) if isinstance(v, (list, tuple)) else v |
| 52 | + for k, v in oestash.ExpandVar(filename=location).items() |
| 53 | + if v |
| 54 | + } |
| 55 | + name = data.get('PN') |
| 56 | + version = data.get('PV') |
| 57 | + description = data.get('DESCRIPTION') |
| 58 | + homepage_url = data.get('HOMEPAGE') |
| 59 | + download_url = data.get('PREMIRRORS') |
| 60 | + declared_license = data.get('LICENSE') |
| 61 | + |
| 62 | + # The item.VarName for SRC_URI[*] from the parser are SRC_URI |
| 63 | + # Therefore, I cannot differentiate md5, sha1, or src file location reference |
| 64 | + # See: https://github.com/priv-kweihmann/oelint-parser/issues/3 |
| 65 | + sha1 = data.get('SRC_URI[sha1sum]') |
| 66 | + md5 = data.get('SRC_URI[md5sum]') |
| 67 | + sha256 = data.get('SRC_URI[sha256sum]') |
| 68 | + sha512 = data.get('SRC_URI[sha512sum]') |
| 69 | + |
| 70 | + dependencies = [] |
| 71 | + |
| 72 | + # Build deps: this is a list of plain BB recipes names |
| 73 | + # https://docs.yoctoproject.org/bitbake/bitbake-user-manual/bitbake-user-manual-ref-variables.html#term-DEPENDS |
| 74 | + build_deps = data.get('DEPENDS', '').split() |
| 75 | + for build_dep in build_deps: |
| 76 | + dep_purl = PackageURL( |
| 77 | + type=cls.default_package_type, |
| 78 | + name=build_dep, |
| 79 | + ).to_string() |
| 80 | + |
| 81 | + dependency = models.DependentPackage( |
| 82 | + purl=dep_purl, |
| 83 | + extracted_requirement=build_dep, |
| 84 | + scope='build', |
| 85 | + is_runtime=False, |
| 86 | + is_optional=True, |
| 87 | + is_resolved=False, |
| 88 | + ) |
| 89 | + dependencies.append(dependency) |
| 90 | + |
| 91 | + # Runtime deps:this is a list of Package names with an optional (=> 12) version constraint |
| 92 | + # https://docs.yoctoproject.org/bitbake/bitbake-user-manual/bitbake-user-manual-ref-variables.html#term-RDEPENDS |
| 93 | + # FIXME: There are some fields such as "RDEPENDS_${PN}" so these may not be correct in all cases |
| 94 | + for key, value in data.items(): |
| 95 | + if not key.startswith('RDEPENDS'): |
| 96 | + continue |
| 97 | + if not value: |
| 98 | + continue |
| 99 | + for name, constraint in get_bitbake_deps(dependencies=value): |
| 100 | + if TRACE: |
| 101 | + logger.debug(f'RDEPENDS: name={name}, constraint={constraint}') |
| 102 | + dep_purl = PackageURL( |
| 103 | + type=cls.default_package_type, |
| 104 | + name=name, |
| 105 | + ).to_string() |
| 106 | + |
| 107 | + extracted_requirement = name |
| 108 | + if constraint: |
| 109 | + extracted_requirement += f' ({constraint})' |
| 110 | + |
| 111 | + dependency = models.DependentPackage( |
| 112 | + purl=dep_purl, |
| 113 | + extracted_requirement=extracted_requirement, |
| 114 | + scope='install', |
| 115 | + is_runtime=True, |
| 116 | + is_optional=False, |
| 117 | + is_resolved=False, |
| 118 | + ) |
| 119 | + |
| 120 | + dependencies.append(dependency) |
| 121 | + |
| 122 | + yield models.PackageData( |
| 123 | + datasource_id=cls.datasource_id, |
| 124 | + type=cls.default_package_type, |
| 125 | + name=name, |
| 126 | + version=version, |
| 127 | + description=description, |
| 128 | + homepage_url=homepage_url, |
| 129 | + download_url=download_url, |
| 130 | + sha1=sha1, |
| 131 | + md5=md5, |
| 132 | + sha256=sha256, |
| 133 | + sha512=sha512, |
| 134 | + declared_license=declared_license, |
| 135 | + dependencies=dependencies, |
| 136 | + ) |
60 | 137 |
|
| 138 | + @classmethod |
| 139 | + def assign_package_to_resources(cls, package, resource, codebase, package_adder): |
| 140 | + return models.DatafileHandler.assign_package_to_parent_tree(package, resource, codebase, package_adder) |
61 | 141 |
|
62 | | -def is_bb_file(location): |
63 | | - return (filetype.is_file(location) |
64 | | - and location.lower().endswith(('.bb',))) |
65 | 142 |
|
66 | | -def parse(location): |
| 143 | +def get_bitbake_deps(dependencies): |
67 | 144 | """ |
68 | | - Return a Package object from an ABOUT file or None. |
| 145 | + Return a list of tuple of (name, version constraint) given a BitBake |
| 146 | + dependencies string. "version constraint" can be None. |
| 147 | +
|
| 148 | + See https://docs.yoctoproject.org/ref-manual/variables.html?#term-RDEPENDS |
| 149 | + For example: |
| 150 | + >>> expected = [('ABC', None), ('abcd', '=>12312'), ('defg', None)] |
| 151 | + >>> result = get_bitbake_deps(" ABC abcd (= > 12312) defg ") |
| 152 | + >>> assert result == expected, result |
| 153 | + >>> expected = [('grub', '==12.23'), ('parted', None), ('e2fsprogs-mke2fs', None)] |
| 154 | + >>> result = get_bitbake_deps("grub (== 12.23) parted e2fsprogs-mke2fs") |
| 155 | + >>> assert result == expected, result |
69 | 156 | """ |
70 | | - if not is_bb_file(location): |
71 | | - return |
72 | | - |
73 | | - _stash = Stash() |
74 | | - # add any bitbake like file |
75 | | - _stash.AddFile(location) |
76 | | - |
77 | | - # Resolves proper cross file dependencies |
78 | | - _stash.Finalize() |
79 | | - |
80 | | - # get all variables of the name PV from all files |
81 | | - package_dict = {} |
82 | | - for item in _stash.GetItemsFor(): |
83 | | - try: |
84 | | - # Create a package dictionary with VarName as the key and |
85 | | - # VarValueStripped as the value |
86 | | - name = item.VarName |
87 | | - value = item.VarValueStripped |
88 | | - try: |
89 | | - if package_dict[name]: |
90 | | - package_dict[name] += '\n' + value |
91 | | - except: |
92 | | - package_dict[name] = value |
93 | | - except: |
94 | | - # Continue to see if there is any VarName value until the end of |
95 | | - # the file |
96 | | - continue |
97 | | - |
98 | | - return build_package(package_dict) |
99 | | - |
100 | | - |
101 | | -def build_package(package_dict): |
| 157 | + return [split_name_constraint(nc) for nc in split_deps(dependencies)] |
| 158 | + |
| 159 | + |
| 160 | +def split_name_constraint(dependency): |
102 | 161 | """ |
103 | | - Return a Package built from `package_dict` obtained from the .bb files. |
| 162 | + Return a tuple (name, version constraint) strings given a name (version |
| 163 | + constraint) BitBake dependency string. |
| 164 | + See https://docs.yoctoproject.org/ref-manual/variables.html?#term-RDEPENDS |
| 165 | + For example: |
| 166 | + >>> assert split_name_constraint(" abcd ( = 12312 ) ") == ("abcd", "=12312",) |
| 167 | + >>> assert split_name_constraint("abcd ") == ("abcd", None) |
104 | 168 | """ |
105 | | - # Initialization |
106 | | - name = None |
107 | | - version = None |
108 | | - description = None |
109 | | - homepage_url = None |
110 | | - download_url = None |
111 | | - sha1 = None |
112 | | - md5 = None |
113 | | - sha256 = None |
114 | | - sha512 = None |
115 | | - declared_license = None |
116 | | - dependencies = None |
117 | | - if 'PN' in package_dict: |
118 | | - name = package_dict['PN'] |
119 | | - if 'PV' in package_dict: |
120 | | - version = package_dict['PV'] |
121 | | - if 'DESCRIPTION' in package_dict: |
122 | | - description = package_dict['DESCRIPTION'] |
123 | | - if 'HOMEPAGE' in package_dict: |
124 | | - homepage_url = package_dict['HOMEPAGE'] |
125 | | - if 'PREMIRRORS' in package_dict: |
126 | | - download_url = package_dict['PREMIRRORS'] |
127 | | - #The item.VarName for SRC_URI[*] from the parser are SRC_URI |
128 | | - #Therefore, I cannot differentiate md5,sha1, or src file location reference |
129 | | - # Entered an issue ticket: https://github.com/priv-kweihmann/oelint-parser/issues/3 |
| 169 | + no_spaces = dependency.replace(' ', '') |
| 170 | + if '(' in no_spaces: |
| 171 | + name, _, constraint = no_spaces.partition('(') |
| 172 | + constraint = constraint.rstrip(')') |
| 173 | + return name, constraint |
| 174 | + return no_spaces, None |
| 175 | + |
| 176 | + |
| 177 | +def split_deps(dependencies): |
130 | 178 | """ |
131 | | - if 'SRC_URI[sha1sum]' in package_dict: |
132 | | - sha1 = package_dict['SRC_URI[sha1sum]'] |
133 | | - if 'SRC_URI[md5sum]' in package_dict: |
134 | | - md5 = package_dict['SRC_URI[md5sum]'] |
135 | | - if 'SRC_URI[sha256sum]' in package_dict: |
136 | | - sha256 = package_dict['SRC_URI[sha256sum]'] |
137 | | - if 'SRC_URI[sha512sum]' in package_dict: |
138 | | - sha512 = package_dict['SRC_URI[sha512sum]'] |
| 179 | + Return a list of name (version constraint) strings given a BitBake |
| 180 | + dependencies string. |
| 181 | +
|
| 182 | + See https://docs.yoctoproject.org/ref-manual/variables.html?#term-RDEPENDS |
| 183 | + For example: |
| 184 | + >>> expected = ['ABC', 'abcd (= > 12312)', 'defg', 'foo', 'bar'] |
| 185 | + >>> result = split_deps(" ABC abcd (= > 12312) defg foo bar ") |
| 186 | + >>> assert result == expected, result |
139 | 187 | """ |
140 | | - if 'LICENSE' in package_dict: |
141 | | - declared_license = package_dict['LICENSE'] |
142 | | - if 'DEPENDS' in package_dict: |
143 | | - if dependencies: |
144 | | - dependencies += '\n' + package_dict['DEPENDS'] |
145 | | - else: |
146 | | - dependencies = package_dict['DEPENDS'] |
147 | | - # There are some RDEPENDS_* fields such as "RDEPENDS_${PN}" which I need to |
148 | | - # check with the substring |
149 | | - for d in package_dict: |
150 | | - if 'RDEPENDS' in d: |
151 | | - if dependencies: |
152 | | - dependencies += '\n' + package_dict[d] |
153 | | - else: |
154 | | - dependencies = package_dict[d] |
155 | | - |
156 | | - return BitbakePackage( |
157 | | - type='bitbake', |
158 | | - name=name, |
159 | | - version=version, |
160 | | - description=description, |
161 | | - homepage_url=homepage_url, |
162 | | - download_url=download_url, |
163 | | - sha1=sha1, |
164 | | - md5=md5, |
165 | | - sha256=sha256, |
166 | | - sha512=sha512, |
167 | | - declared_license=declared_license, |
168 | | - dependencies=dependencies |
169 | | - ) |
| 188 | + normalized_spaces = ' '.join(dependencies.split()) |
| 189 | + name = r'\w[\w\d_-]+' |
| 190 | + version_constraint = r'\([<>= ]+[^<>= ]+\s?\)' |
| 191 | + splitter = re.compile(fr'({name}\s?(?:{version_constraint})?)').findall |
| 192 | + return [s.strip() for s in splitter(normalized_spaces)] |
0 commit comments