|
9 | 9 | # |
10 | 10 |
|
11 | 11 | import ast |
| 12 | +from distutils.core import setup |
12 | 13 | import io |
13 | 14 | import json |
14 | 15 | import logging |
@@ -132,66 +133,128 @@ class BaseExtractedPythonLayout(BasePypiHandler): |
132 | 133 | def assemble(cls, package_data, resource, codebase): |
133 | 134 | # a source distribution can have many manifests |
134 | 135 | datafile_name_patterns = ( |
135 | | - 'PKG-INFO', |
136 | | - 'setup.py', |
137 | | - 'setup.cfg', |
138 | 136 | 'Pipfile.lock', |
139 | 137 | 'Pipfile', |
140 | 138 | ) + PipRequirementsFileHandler.path_patterns |
141 | 139 |
|
| 140 | + # TODO: we want PKG-INFO first, then (setup.py, setup.cfg), then pyproject.toml for poetry |
| 141 | + # then we have the rest of the lock files (pipfile, pipfile.lock, etc.) |
| 142 | + |
142 | 143 | package_resource = None |
143 | | - if resource.name in datafile_name_patterns: |
| 144 | + if resource.name == 'PKG-INFO': |
144 | 145 | package_resource = resource |
145 | | - |
| 146 | + elif resource.name in datafile_name_patterns: |
| 147 | + if resource.has_parent(): |
| 148 | + siblings = resource.siblings(codebase) |
| 149 | + package_resource = [r for r in siblings if r.name == 'PKG-INFO'] |
| 150 | + if package_resource: |
| 151 | + package_resource = package_resource[0] |
| 152 | + |
| 153 | + package = None |
146 | 154 | if package_resource: |
147 | | - # do we have enough to create a package? |
148 | | - if package_data.purl: |
| 155 | + pkg_data = package_resource.package_data[0] |
| 156 | + pkg_data = models.PackageData.from_dict(pkg_data) |
| 157 | + if pkg_data.purl: |
149 | 158 | package = models.Package.from_package_data( |
150 | | - package_data=package_data, |
| 159 | + package_data=pkg_data, |
151 | 160 | datafile_path=package_resource.path, |
152 | 161 | ) |
153 | | - package_uid = package.package_uid |
154 | | - |
155 | | - if not package.license_expression: |
156 | | - package.license_expression = compute_normalized_license(package.declared_license) |
157 | | - |
158 | | - root = package_resource.parent(codebase) |
159 | | - if root: |
160 | | - for py_res in root.walk(codebase): |
161 | | - if py_res.is_dir: |
162 | | - continue |
163 | | - if package_uid not in py_res.for_packages: |
164 | | - py_res.for_packages.append(package_uid) |
165 | | - py_res.save(codebase) |
166 | | - yield py_res |
167 | | - elif codebase.has_single_resource: |
168 | | - if package_uid not in package_resource.for_packages: |
169 | | - package_resource.for_packages.append(package_uid) |
170 | | - package_resource.save(codebase) |
171 | | - yield package_resource |
172 | | - yield package |
173 | | - else: |
174 | | - # we have no package, so deps are not for a specific package uid |
175 | | - package_uid = None |
| 162 | + package_resource.for_packages.append(package.package_uid) |
| 163 | + package_resource.save(codebase) |
| 164 | + yield package_resource |
176 | 165 |
|
177 | | - # in all cases yield possible dependencies |
178 | | - yield from yield_dependencies_from_package_data(package_data, package_resource.path, package_uid) |
179 | | - yield package_resource |
| 166 | + yield from yield_dependencies_from_package_data( |
| 167 | + package_data=pkg_data, |
| 168 | + datafile_path=package_resource.path, |
| 169 | + package_uid=package.package_uid |
| 170 | + ) |
| 171 | + else: |
| 172 | + setup_resources = [] |
| 173 | + if resource.has_parent(): |
| 174 | + siblings = resource.siblings(codebase) |
| 175 | + setup_resources = [r for r in siblings if r.name in ('setup.py', 'setup.cfg')] |
| 176 | + setup_package_data = [ |
| 177 | + (setup_resource, models.PackageData.from_dict(setup_resource.package_data[0])) |
| 178 | + for setup_resource in setup_resources |
| 179 | + ] |
| 180 | + setup_package_data = sorted(setup_package_data, key=lambda s: bool(s[1].purl), reverse=True) |
| 181 | + for setup_resource, setup_pkg_data in setup_package_data: |
| 182 | + if setup_pkg_data.purl: |
| 183 | + if not package: |
| 184 | + package = models.Package.from_package_data( |
| 185 | + package_data=setup_pkg_data, |
| 186 | + datafile_path=setup_resource.path, |
| 187 | + ) |
| 188 | + package_resource = setup_resource |
| 189 | + else: |
| 190 | + package.update(setup_pkg_data, setup_resource.path) |
| 191 | + if package: |
| 192 | + for setup_resource, setup_pkg_data in setup_package_data: |
| 193 | + setup_resource.for_packages.append(package.package_uid) |
| 194 | + setup_resource.save(codebase) |
| 195 | + yield setup_resource |
| 196 | + |
| 197 | + yield from yield_dependencies_from_package_data( |
| 198 | + package_data=setup_pkg_data, |
| 199 | + datafile_path=setup_resource.path, |
| 200 | + package_uid=package.package_uid |
| 201 | + ) |
| 202 | + |
| 203 | + if package: |
| 204 | + if not package.license_expression: |
| 205 | + package.license_expression = compute_normalized_license(package.declared_license) |
| 206 | + package_uid = package.package_uid |
| 207 | + |
| 208 | + root = package_resource.parent(codebase) |
| 209 | + if root: |
| 210 | + for py_res in cls.walk_pypi(resource=root, codebase=codebase): |
| 211 | + if py_res.is_dir: |
| 212 | + continue |
| 213 | + if package_uid not in py_res.for_packages: |
| 214 | + py_res.for_packages.append(package_uid) |
| 215 | + py_res.save(codebase) |
| 216 | + yield py_res |
| 217 | + elif codebase.has_single_resource: |
| 218 | + if package_uid not in package_resource.for_packages: |
| 219 | + package_resource.for_packages.append(package_uid) |
| 220 | + package_resource.save(codebase) |
180 | 221 |
|
181 | | - for sibling in package_resource.siblings(codebase): |
182 | | - if sibling.name in datafile_name_patterns: |
183 | | - yield from yield_dependencies_from_package_resource(sibling, package_uid) |
| 222 | + yield package |
184 | 223 |
|
185 | | - if package_uid not in sibling.for_packages: |
186 | | - sibling.for_packages.append(package_uid) |
187 | | - sibling.save(codebase) |
188 | | - yield sibling |
189 | 224 | else: |
190 | | - yield from yield_dependencies_from_package_resource(resource) |
| 225 | + package_uid = None |
| 226 | + |
| 227 | + for sibling in package_resource.siblings(codebase): |
| 228 | + if sibling.name in datafile_name_patterns: |
| 229 | + yield from yield_dependencies_from_package_resource( |
| 230 | + resource=sibling, |
| 231 | + package_uid=package_uid |
| 232 | + ) |
| 233 | + |
| 234 | + if package_uid and package_uid not in sibling.for_packages: |
| 235 | + sibling.for_packages.append(package_uid) |
| 236 | + sibling.save(codebase) |
| 237 | + yield sibling |
191 | 238 |
|
192 | 239 | @classmethod |
193 | | - def assign_package_to_resources(cls, package, resource, codebase): |
194 | | - return models.DatafileHandler.assign_package_to_parent_tree(package, resource, codebase) |
| 240 | + def walk_pypi(cls, resource, codebase): |
| 241 | + """ |
| 242 | + Walk the ``codebase`` Codebase top-down, breadth-first starting from the |
| 243 | + ``resource`` Resource. |
| 244 | +
|
| 245 | + Skip the directory named "site-packages": this avoids |
| 246 | + reporting nested vendored packages as being part of their parent. |
| 247 | + Instead they will be reported on their own. |
| 248 | + """ |
| 249 | + for child in resource.children(codebase): |
| 250 | + if child.name == 'site-packages': |
| 251 | + continue |
| 252 | + |
| 253 | + yield child |
| 254 | + |
| 255 | + if child.is_dir: |
| 256 | + for subchild in cls.walk_pypi(child, codebase): |
| 257 | + yield subchild |
195 | 258 |
|
196 | 259 |
|
197 | 260 | class PythonSdistPkgInfoFile(BaseExtractedPythonLayout): |
@@ -697,31 +760,6 @@ def parse(cls, location): |
697 | 760 |
|
698 | 761 |
|
699 | 762 | class PipRequirementsFileHandler(BaseDependencyFileHandler): |
700 | | - """ |
701 | | - A pip requirements (or constraints) file. |
702 | | -
|
703 | | - Some example:: |
704 | | - >>> PipRequirementsFileHandler.is_datafile('dev-requirements.txt', _bare_filename=True) |
705 | | - True |
706 | | - >>> PipRequirementsFileHandler.is_datafile('requirements.txt', _bare_filename=True) |
707 | | - True |
708 | | - >>> PipRequirementsFileHandler.is_datafile('requirement.txt', _bare_filename=True) |
709 | | - True |
710 | | - >>> PipRequirementsFileHandler.is_datafile('requirements.in', _bare_filename=True) |
711 | | - True |
712 | | - >>> PipRequirementsFileHandler.is_datafile('requirements.pip', _bare_filename=True) |
713 | | - True |
714 | | - >>> PipRequirementsFileHandler.is_datafile('requirements-dev.txt', _bare_filename=True) |
715 | | - True |
716 | | - >>> PipRequirementsFileHandler.is_datafile('some-requirements-dev.txt', _bare_filename=True) |
717 | | - True |
718 | | - >>> PipRequirementsFileHandler.is_datafile('requires.txt', _bare_filename=True) |
719 | | - True |
720 | | - >>> PipRequirementsFileHandler.is_datafile('requirements/base.txt', _bare_filename=True) |
721 | | - True |
722 | | - >>> PipRequirementsFileHandler.is_datafile('reqs.txt', _bare_filename=True) |
723 | | - True |
724 | | - """ |
725 | 763 | datasource_id = 'pip_requirements' |
726 | 764 |
|
727 | 765 | path_patterns = ( |
|
0 commit comments