Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Next release
``licensedcode-data``.
https://github.com/aboutcode-org/scancode-toolkit/pull/5056

- Add support for PEP 751 ``pylock.toml`` standardized Python lockfiles. A new
``pypi_pylock_toml`` package datafile handler parses ``pylock.toml`` and
``pylock.<name>.toml`` files into resolved PyPI dependencies with their
versions, hashes and sources.
https://github.com/aboutcode-org/scancode-toolkit/issues/4962

v33.0.0rc1 - 2026-05-14
------------------------

Expand Down
94 changes: 28 additions & 66 deletions docs/source/reference/scancode-supported-packages.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.. _supported-packages:


.. _supported_packages:

Supported package manifests and package datafiles
-------------------------------------------------

ScanCode supports a wide variety of package manifests, lockfiles
Scancode supports a wide variety of package manifests, lockfiles
and other package datafiles containing package and dependency
information.

Expand Down Expand Up @@ -138,13 +140,6 @@ parsers in scancode-toolkit during documentation builds.
- ``cargo_toml``
- Rust
- https://doc.rust-lang.org/cargo/reference/manifest.html
* - Rust binary
- None
- ``cargo``
- ``linux``, ``win``, ``mac``
- ``rust_binary``
- Rust
- https://github.com/rust-secure-code/cargo-auditable/blob/master/PARSING.md
* - Chef cookbook metadata.json
- ``*/metadata.json``
- ``chef``
Expand Down Expand Up @@ -442,7 +437,7 @@ parsers in scancode-toolkit during documentation builds.
- https://guides.rubygems.org/specification-reference/
* - RubyGems Bundler Gemfile
- ``*/Gemfile``
``*/*.gemfile``
``*.gemfile``
``*/Gemfile-*``
- ``gem``
- ``linux``, ``win``, ``mac``
Expand Down Expand Up @@ -505,13 +500,6 @@ parsers in scancode-toolkit during documentation builds.
- ``godeps``
- Go
- https://github.com/tools/godep
* - Go binary
- None
- ``golang``
- ``linux``, ``win``, ``mac``
- ``golang_binary``
- Go
- https://github.com/nexB/go-inspector/
* - Haxe haxelib.json metadata file
- ``*/haxelib.json``
- ``haxe``
Expand Down Expand Up @@ -622,13 +610,6 @@ parsers in scancode-toolkit during documentation builds.
- ``mozilla_xpi``
- JavaScript
- https://en.wikipedia.org/wiki/XPInstall
* - Microsoft MSI installer
- ``*.msi``
- ``msi``
- ``linux``
- ``msi_installer``
- None
- https://docs.microsoft.com/en-us/windows/win32/msi/windows-installer-portal
* - npm package.json
- ``*/package.json``
- ``npm``
Expand Down Expand Up @@ -812,6 +793,14 @@ parsers in scancode-toolkit during documentation builds.
- ``pypi_poetry_pyproject_toml``
- Python
- https://packaging.python.org/en/latest/specifications/pyproject-toml/
* - PEP 751 pylock.toml Python lockfile
- ``*pylock.toml``
``*pylock.*.toml``
- ``pypi``
- ``linux``, ``win``, ``mac``
- ``pypi_pylock_toml``
- Python
- https://peps.python.org/pep-0751/
* - Python pyproject.toml
- ``*pyproject.toml``
- ``pypi``
Expand Down Expand Up @@ -840,6 +829,20 @@ parsers in scancode-toolkit during documentation builds.
- ``pypi_setup_py``
- Python
- https://docs.python.org/3.11/distutils/setupscript.html
* - Python UV lockfile
- ``*uv.lock``
- ``pypi``
- ``linux``, ``win``, ``mac``
- ``pypi_uv_lock``
- Python
- https://docs.astral.sh/uv/concepts/projects/sync/#the-uvlock-file
* - Python UV pyproject.toml
- ``*pyproject.toml``
- ``pypi``
- ``linux``, ``win``, ``mac``
- ``pypi_uv_pyproject_toml``
- Python
- https://docs.astral.sh/uv/concepts/projects/
* - PyPI wheel
- ``*.whl``
- ``pypi``
Expand Down Expand Up @@ -876,27 +879,6 @@ parsers in scancode-toolkit during documentation builds.
- ``rpm_archive``
- None
- https://en.wikipedia.org/wiki/RPM_Package_Manager
* - RPM installed package BDB database
- ``*var/lib/rpm/Packages``
- ``rpm``
- ``linux``
- ``rpm_installed_database_bdb``
- None
- https://man7.org/linux/man-pages/man8/rpmdb.8.html
* - RPM installed package NDB database
- ``*usr/lib/sysimage/rpm/Packages.db``
- ``rpm``
- ``linux``
- ``rpm_installed_database_ndb``
- None
- https://fedoraproject.org/wiki/Changes/NewRpmDBFormat
* - RPM installed package SQLite database
- ``*rpm/rpmdb.sqlite``
- ``rpm``
- ``linux``
- ``rpm_installed_database_sqlite``
- None
- https://fedoraproject.org/wiki/Changes/Sqlite_Rpmdb
* - RPM mariner distroless package manifest
- ``*var/lib/rpmmanifest/container-manifest-2``
- ``rpm``
Expand Down Expand Up @@ -970,27 +952,6 @@ parsers in scancode-toolkit during documentation builds.
- ``java_war_web_xml``
- Java
- https://en.wikipedia.org/wiki/WAR_(file_format)
* - Windows Registry Installed Program - Docker SOFTWARE
- ``*/Files/Windows/System32/config/SOFTWARE``
- ``windows-program``
- ``linux``
- ``win_reg_installed_programs_docker_file_software``
- None
- https://en.wikipedia.org/wiki/Windows_Registry
* - Windows Registry Installed Program - Docker Software Delta
- ``*/Hives/Software_Delta``
- ``windows-program``
- ``linux``
- ``win_reg_installed_programs_docker_software_delta``
- None
- https://en.wikipedia.org/wiki/Windows_Registry
* - Windows Registry Installed Program - Docker UtilityVM SOFTWARE
- ``*/UtilityVM/Files/Windows/System32/config/SOFTWARE``
- ``windows-program``
- ``linux``
- ``win_reg_installed_programs_docker_utility_software``
- None
- https://en.wikipedia.org/wiki/Windows_Registry
* - Microsoft Update Manifest .mum file
- ``*.mum``
- ``windows-update``
Expand Down Expand Up @@ -1021,3 +982,4 @@ parsers in scancode-toolkit during documentation builds.
- ``windows_executable``
- None
- https://en.wikipedia.org/wiki/Portable_Executable

1 change: 1 addition & 0 deletions src/packagedcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
pypi.PoetryLockHandler,
pypi.UvPyprojectTomlHandler,
pypi.UvLockHandler,
pypi.PylockTomlHandler,
pypi.PythonEditableInstallationPkgInfoFile,
pypi.PythonEggPkgInfoFile,
pypi.PythonInstalledWheelMetadataFile,
Expand Down
174 changes: 174 additions & 0 deletions src/packagedcode/pypi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,180 @@ def parse(cls, location, package_only=False):
yield models.PackageData.from_data(package_data, package_only)


class PylockTomlHandler(models.DatafileHandler):
datasource_id = 'pypi_pylock_toml'
path_patterns = ('*pylock.toml', '*pylock.*.toml',)
default_package_type = 'pypi'
default_primary_language = 'Python'
description = 'PEP 751 pylock.toml Python lockfile'
documentation_url = 'https://peps.python.org/pep-0751/'

@classmethod
def parse(cls, location, package_only=False):
with open(location, "rb") as fp:
toml_data = tomllib.load(fp)

# PEP 751 uses a top-level ``packages`` array (note: plural, unlike the
# ``package`` key used by uv.lock and poetry.lock).
packages = toml_data.get('packages')
if not packages:
return

dependencies = []
for package in packages:
name = package.get('name')
if not name:
continue
version = package.get('version')

# ``[[packages.dependencies]]`` are edges referencing other locked
# packages by name (the resolved dependency graph).
dependencies_for_resolved = []
for dep in (package.get('dependencies') or []):
dep_name = dep.get('name')
if not dep_name:
continue
dep_purl = PackageURL(type=cls.default_package_type, name=dep_name)
dependencies_for_resolved.append(
models.DependentPackage(
purl=dep_purl.to_string(),
extracted_requirement=None,
scope='dependencies',
is_runtime=True,
is_optional=False,
is_direct=True,
is_pinned=False,
).to_dict()
)

# PEP 751 package sources are mutually exclusive: an ``index``
# (with ``sdist`` and/or ``wheels``), ``vcs``, ``directory`` or
# ``archive``. Unlike uv.lock, hashes are stored as a table keyed
# by algorithm (e.g. ``hashes.sha256``) rather than a
# ``"sha256:<hex>"`` string.
download_url = None
sha256 = None
file_name = None
vcs_url = None
extra_data = {}

sdist = package.get('sdist')
wheels = package.get('wheels')
archive = package.get('archive')
vcs = package.get('vcs')
directory = package.get('directory')
index = package.get('index')

if isinstance(sdist, dict):
download_url = sdist.get('url')
sha256 = (sdist.get('hashes') or {}).get('sha256')
elif wheels:
first_wheel = wheels[0] or {}
download_url = first_wheel.get('url')
sha256 = (first_wheel.get('hashes') or {}).get('sha256')
elif isinstance(archive, dict):
download_url = archive.get('url')
sha256 = (archive.get('hashes') or {}).get('sha256')
extra_data['source_type'] = 'archive'
elif isinstance(vcs, dict):
vcs_url = vcs.get('url')
extra_data['source_type'] = 'vcs'
commit_id = vcs.get('commit-id')
if commit_id:
extra_data['vcs_commit_id'] = commit_id
elif isinstance(directory, dict):
extra_data['source_type'] = 'directory'
dir_path = directory.get('path')
if dir_path:
extra_data['directory'] = dir_path

if index:
extra_data['index'] = index

marker = package.get('marker')
if marker:
extra_data['marker'] = marker

if download_url:
file_name = posixpath.basename(download_url) or None

# Only synthesize PyPI URLs for packages actually sourced from a
# PyPI-style index. For ``vcs``, ``directory`` or ``archive``
# sources we record only what the lock file provides (e.g. the
# archive ``url`` as the download URL, the git URL in ``vcs_url``)
# rather than inventing a pypi.org URL that does not exist.
if index or isinstance(sdist, dict) or wheels:
urls = get_pypi_urls(name, version)
if download_url:
# prefer the exact artifact URL recorded in the lock file
urls['repository_download_url'] = download_url
else:
urls = dict(
repository_homepage_url=None,
repository_download_url=download_url,
api_data_url=None,
)

qualifiers = {}
if file_name:
# per purl-spec PyPI definition the artifact ``file_name`` is
# carried as a purl qualifier so the purl identifies the
# specific artifact recorded in the lock file.
qualifiers['file_name'] = file_name

resolved_package_data = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
primary_language='Python',
name=name,
version=version,
qualifiers=qualifiers,
sha256=sha256,
vcs_url=vcs_url,
is_virtual=True,
dependencies=dependencies_for_resolved,
extra_data=extra_data,
**urls,
)
resolved_package = models.PackageData.from_data(resolved_package_data, package_only)

dependencies.append(
models.DependentPackage(
purl=resolved_package.purl,
extracted_requirement=None,
scope=None,
is_runtime=True,
is_optional=False,
is_direct=False,
is_pinned=bool(version),
resolved_package=resolved_package.to_dict(),
).to_dict()
)

extra_data = {}
lock_version = toml_data.get('lock-version')
if lock_version is not None:
extra_data['lock_version'] = lock_version
requires_python = toml_data.get('requires-python')
if requires_python:
extra_data['python_requires'] = requires_python
created_by = toml_data.get('created-by')
if created_by:
extra_data['created_by'] = created_by
environments = toml_data.get('environments')
if environments:
extra_data['environments'] = environments

package_data = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
primary_language='Python',
extra_data=extra_data,
dependencies=dependencies,
)
yield models.PackageData.from_data(package_data, package_only)


class PipInspectDeplockHandler(models.DatafileHandler):
datasource_id = 'pypi_inspect_deplock'
path_patterns = ('*pip-inspect.deplock',)
Expand Down
7 changes: 7 additions & 0 deletions tests/packagedcode/data/plugin/plugins_list_linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,13 @@ Package type: pypi
description: Python poetry pyproject.toml
path_patterns: '*pyproject.toml'
--------------------------------------------
Package type: pypi
datasource_id: pypi_pylock_toml
documentation URL: https://peps.python.org/pep-0751/
primary language: Python
description: PEP 751 pylock.toml Python lockfile
path_patterns: '*pylock.toml', '*pylock.*.toml'
--------------------------------------------
Package type: pypi
datasource_id: pypi_pyproject_toml
documentation URL: https://packaging.python.org/en/latest/specifications/pyproject-toml/
Expand Down
9 changes: 9 additions & 0 deletions tests/packagedcode/data/pypi/pylock/named/pylock.dev.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
lock-version = "1.0"
requires-python = ">=3.9"
created-by = "pip"

[[packages]]
name = "click"
version = "8.1.7"
index = "https://pypi.org/simple"
sdist = {name = "click-8.1.7.tar.gz", url = "https://files.pythonhosted.org/packages/source/c/click/click-8.1.7.tar.gz", hashes = {sha256 = "ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}}
Loading
Loading