Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parser for swift-show-dependencies.deplock #3829

Merged
merged 8 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ v33.0.0 (next next, roadmap)
from nuget lockfile `packages.lock.json`.
See https://github.com/nexB/scancode-toolkit/pull/3825

- Add support for parsing packages and dependency relationships
from swift `swift-show-dependencies.deplock` generated by DepLock.
See https://github.com/nexB/scancode-toolkit/pull/3829

v32.2.0 - 2024-06-19
----------------------

Expand Down
9 changes: 8 additions & 1 deletion docs/source/reference/available_package_parsers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -766,8 +766,9 @@ parsers in scancode-toolkit during documentation builds.
- ``squashfs_disk_image``
- None
- https://en.wikipedia.org/wiki/SquashFS
* - JSON dump of Package.swift created with ``swift package dump-package > Package.swift.json``
* - JSON dump of Package.swift created by DepLock or with ``swift package dump-package > Package.swift.json``
- ``*/Package.swift.json``
``*/Package.swift.deplock``
- ``swift``
- ``swift_package_manifest_json``
- Swift
Expand All @@ -779,6 +780,12 @@ parsers in scancode-toolkit during documentation builds.
- ``swift_package_resolved``
- swift
- https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html#package-dependency
* - Swift dependency graph created by DepLock
- ``*/swift-show-dependencies.deplock``
- ``swift``
- ``swift_package_show_dependencies``
- Swift
- https://forums.swift.org/t/swiftpm-show-dependencies-without-fetching-dependencies/51154
* - Java Web Application Archive
- ``*.war``
- ``war``
Expand Down
1 change: 1 addition & 0 deletions src/packagedcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@

swift.SwiftManifestJsonHandler,
swift.SwiftPackageResolvedHandler,
swift.SwiftShowDependenciesDepLockHandler,

windows.MicrosoftUpdateManifestHandler,

Expand Down
255 changes: 227 additions & 28 deletions src/packagedcode/swift.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,75 @@ def logger_debug(*args):
return logger.debug(" ".join(isinstance(a, str) and a or repr(a) for a in args))


class SwiftShowDependenciesDepLockHandler(models.DatafileHandler):
datasource_id = "swift_package_show_dependencies"
path_patterns = ("*/swift-show-dependencies.deplock",)
default_package_type = "swift"
default_primary_language = "Swift"
description = "Swift dependency graph created by DepLock"
documentation_url = "https://forums.swift.org/t/swiftpm-show-dependencies-without-fetching-dependencies/51154"

@classmethod
def _parse(cls, swift_dependency_relation, package_only=False):

if TRACE:
logger_debug(
f"SwiftShowDependenciesDepLockHandler: deplock: package: {swift_dependency_relation}"
)

dependencies = get_flatten_dependencies(
swift_dependency_relation.get("dependencies")
keshav-space marked this conversation as resolved.
Show resolved Hide resolved
)

package_data = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
primary_language=cls.default_primary_language,
namespace=None,
keshav-space marked this conversation as resolved.
Show resolved Hide resolved
name=swift_dependency_relation.get("name"),
dependencies=dependencies,
)

return models.PackageData.from_data(package_data, package_only)

@classmethod
def parse(cls, location, package_only=False):
with io.open(location, encoding="utf-8") as loc:
swift_dependency_relation = json.load(loc)

yield cls._parse(swift_dependency_relation, package_only)

@classmethod
def assemble(
cls, package_data, resource, codebase, package_adder=models.add_to_package
):
siblings = resource.siblings(codebase)
swift_manifest_resource = [
r
for r in siblings
if r.name in ("Package.swift.json", "Package.swift.deplock")
]

# Skip the assembly if the Swift manifest is present.
keshav-space marked this conversation as resolved.
Show resolved Hide resolved
# SwiftManifestJsonHandler's assembly will take care of the
# dependencies from swift-show-dependencies.deplock file.
if swift_manifest_resource:
return []

yield from super(SwiftShowDependenciesDepLockHandler, cls).assemble(
package_data=package_data,
resource=resource,
codebase=codebase,
package_adder=package_adder,
)


class SwiftManifestJsonHandler(models.DatafileHandler):
datasource_id = "swift_package_manifest_json"
path_patterns = ("*/Package.swift.json",)
path_patterns = ("*/Package.swift.json", "*/Package.swift.deplock")
default_package_type = "swift"
default_primary_language = "Swift"
description = "JSON dump of Package.swift created with ``swift package dump-package > Package.swift.json``"
description = "JSON dump of Package.swift created by DepLock or with ``swift package dump-package > Package.swift.json``"
documentation_url = "https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html"

@classmethod
Expand Down Expand Up @@ -98,41 +161,64 @@ def assemble(
top-level package with resolved dependencies.
"""
siblings = resource.siblings(codebase)
processed_dependencies = []
swift_resolved_package_resource = [
r for r in siblings if r.name == "Package.resolved"
]
dependencies_from_manifest = package_data.dependencies

processed_dependencies = []
if swift_resolved_package_resource:
swift_resolved_package_resource = swift_resolved_package_resource[0]
swift_resolved_package_data = swift_resolved_package_resource.package_data
swift_show_dependencies_resources = [
r for r in siblings if r.name == "swift-show-dependencies.deplock"
]

for package in swift_resolved_package_data:
version = package.get("version")
name = package.get("name")
if swift_show_dependencies_resources:
swift_show_dependencies_resource = swift_show_dependencies_resources[0]
swift_show_dependencies_package_data = (
swift_show_dependencies_resource.package_data
)

# Dependencies from `swift-show-dependencies.deplock` supersede dependencies from other datafiles.
processed_dependencies = swift_show_dependencies_package_data[0][
"dependencies"
]
processed_dependencies = [
models.DependentPackage.from_dict(i) for i in processed_dependencies
]

# Use dependencies from `Package.resolved` when `swift-show-dependencies.deplock` is not present.
else:
dependencies_from_manifest = package_data.dependencies

purl = PackageURL(
type=cls.default_package_type, name=name, version=version
if swift_resolved_package_resource:
swift_resolved_package_resource = swift_resolved_package_resource[0]
swift_resolved_package_data = (
swift_resolved_package_resource.package_data
)
processed_dependencies.append(
models.DependentPackage(
purl=purl.to_string(),
scope="dependencies",
is_runtime=True,
is_optional=False,
is_resolved=True,
extracted_requirement=version,

for package in swift_resolved_package_data:
version = package.get("version")
name = package.get("name")

purl = PackageURL(
type=cls.default_package_type, name=name, version=version
)
processed_dependencies.append(
models.DependentPackage(
purl=purl.to_string(),
scope="dependencies",
is_runtime=True,
is_optional=False,
is_resolved=True,
extracted_requirement=version,
)
)
)

for dependency in dependencies_from_manifest[:]:
dependency_purl = PackageURL.from_string(dependency.purl)
for dependency in dependencies_from_manifest[:]:
dependency_purl = PackageURL.from_string(dependency.purl)

if dependency_purl.name == name:
dependencies_from_manifest.remove(dependency)
if dependency_purl.name == name:
dependencies_from_manifest.remove(dependency)

processed_dependencies.extend(dependencies_from_manifest)
processed_dependencies.extend(dependencies_from_manifest)

datafile_path = resource.path
if package_data.purl:
Expand All @@ -141,7 +227,12 @@ def assemble(
datafile_path=datafile_path,
)

if swift_resolved_package_resource:
if swift_show_dependencies_resources:
package.datafile_paths.append(swift_show_dependencies_resource.path)
package.datasource_ids.append(
SwiftShowDependenciesDepLockHandler.datasource_id
)
elif swift_resolved_package_resource:
package.datafile_paths.append(swift_resolved_package_resource.path)
package.datasource_ids.append(SwiftPackageResolvedHandler.datasource_id)

Expand Down Expand Up @@ -196,7 +287,9 @@ def assemble(
):
siblings = resource.siblings(codebase)
swift_manifest_resource = [
r for r in siblings if r.name == "Package.swift.json"
r
for r in siblings
if r.name in ("Package.swift.json", "Package.swift.deplock")
]

# Skip the assembly if the ``Package.swift.json`` manifest is present.
Expand Down Expand Up @@ -328,3 +421,109 @@ def get_namespace_and_name(url):
canonical_name = hostname + path

return canonical_name.rsplit("/", 1)


def get_flatten_dependencies(dependency_tree):
"""
Get the list of dependencies from the dependency graph where each
element is a DependentPackage containing its 1st order dependencies.
"""
dependencies = []
transitive_dependencies = []

# process direct dependency
for dependency in dependency_tree:
transitives = dependency.get("dependencies", [])
transitive_dependencies.append(transitives)
parent_child_dep = get_dependent_package_from_subtree(
dependency=dependency,
is_top_level_dependency=True,
)
dependencies.append(parent_child_dep)

# process all transitive dependencies
while transitive_dependencies:
transitive_dependency_tree = transitive_dependencies.pop(0)
if not transitive_dependency_tree:
continue

for transitive in transitive_dependency_tree:
dependencies_of_transitive_dependency = transitive.get("dependencies", [])
# add nested dependencies in transitive_dependencies queue for processing
transitive_dependencies.append(dependencies_of_transitive_dependency)

parent_child_dep = get_dependent_package_from_subtree(
dependency=transitive,
is_top_level_dependency=False,
)

dependencies.append(parent_child_dep)

return dependencies


def get_dependent_package_from_subtree(dependency, is_top_level_dependency):
"""
Get the DependentPackage for a ``dependency`` subtree along with its 1st
order dependencies. Set `is_direct` to True if the subtree is a direct
dependency for the top-level package.
"""
dependencies_of_parent = []
repository_url = dependency.get("url")
version = dependency.get("version")
transitives = dependency.get("dependencies", [])
namespace, name = get_namespace_and_name(repository_url)
purl = PackageURL(
type="swift",
namespace=namespace,
name=name,
version=version,
)

for transitive in transitives:
transitive_repository_url = transitive.get("url")
transitive_version = transitive.get("version")
transitive_namespace, transitive_name = get_namespace_and_name(
transitive_repository_url
)
transitive_purl = PackageURL(
type="swift",
namespace=transitive_namespace,
name=transitive_name,
version=transitive_version,
)

child_dependency = models.DependentPackage(
purl=transitive_purl.to_string(),
scope="dependencies",
extracted_requirement=transitive_version,
is_runtime=False,
is_optional=False,
is_resolved=True,
is_direct=True,
).to_dict()

dependencies_of_parent.append(child_dependency)

parent_package_data_mapping = dict(
datasource_id=SwiftShowDependenciesDepLockHandler.datasource_id,
type=SwiftShowDependenciesDepLockHandler.default_package_type,
primary_language=SwiftShowDependenciesDepLockHandler.default_primary_language,
namespace=namespace,
name=name,
version=version,
dependencies=dependencies_of_parent,
is_virtual=True,
)
parent_dependency = models.PackageData.from_data(parent_package_data_mapping)

return models.DependentPackage(
purl=purl.to_string(),
scope="dependencies",
extracted_requirement=version,
is_runtime=False,
is_optional=False,
is_resolved=True,
is_direct=is_top_level_dependency,
resolved_package=parent_dependency,
)
11 changes: 9 additions & 2 deletions tests/packagedcode/data/plugin/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -836,8 +836,8 @@ Package type: swift
datasource_id: swift_package_manifest_json
documentation URL: https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html
primary language: Swift
description: JSON dump of Package.swift created with ``swift package dump-package > Package.swift.json``
path_patterns: '*/Package.swift.json'
description: JSON dump of Package.swift created by DepLock or with ``swift package dump-package > Package.swift.json``
path_patterns: '*/Package.swift.json', '*/Package.swift.deplock'
--------------------------------------------
Package type: swift
datasource_id: swift_package_resolved
Expand All @@ -846,6 +846,13 @@ Package type: swift
description: Resolved full dependency lockfile for Package.swift created with ``swift package resolve``
path_patterns: '*/Package.resolved', '*/.package.resolved'
--------------------------------------------
Package type: swift
datasource_id: swift_package_show_dependencies
documentation URL: https://forums.swift.org/t/swiftpm-show-dependencies-without-fetching-dependencies/51154
primary language: Swift
description: Swift dependency graph created by DepLock
path_patterns: '*/swift-show-dependencies.deplock'
--------------------------------------------
Package type: war
datasource_id: java_war_archive
documentation URL: https://en.wikipedia.org/wiki/WAR_(file_format)
Expand Down
Loading
Loading