diff --git a/CHANGELOG.md b/CHANGELOG.md index ba024f5e0..b79c7be1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Sandboxed processes sticking around after CLI is killed with a signal +- Lockfiles with local versions breaking the pip parser ## 6.3.0 - 2024-04-18 diff --git a/lockfile/src/lib.rs b/lockfile/src/lib.rs index b152c9fb2..4da82a90c 100644 --- a/lockfile/src/lib.rs +++ b/lockfile/src/lib.rs @@ -217,6 +217,11 @@ pub enum PackageVersion { Git(String), /// Version distributed over HTTP(S). DownloadUrl(String), + /// Untracked version source. + /// + /// Used for package versions which cannot clearly be identified, like + /// Python's local version. + Unknown, } /// Version from a foreign package registry. diff --git a/lockfile/src/parse_depfile.rs b/lockfile/src/parse_depfile.rs index effad99e5..914cbceb4 100644 --- a/lockfile/src/parse_depfile.rs +++ b/lockfile/src/parse_depfile.rs @@ -192,6 +192,10 @@ fn filter_packages(mut packages: Vec) -> Vec { log::debug!("Ignoring remote dependency {} ({url:?})", package.name); return None; }, + PackageVersion::Unknown => { + log::debug!("Ignoring dependency {}", package.name); + return None; + }, }; Some(PackageDescriptor { diff --git a/lockfile/src/parsers/pypi.rs b/lockfile/src/parsers/pypi.rs index 4064e3b5a..950bb4796 100644 --- a/lockfile/src/parsers/pypi.rs +++ b/lockfile/src/parsers/pypi.rs @@ -102,12 +102,17 @@ fn package<'a>(input: &'a str, registry: Option<&str>) -> IResult<&'a str, Packa // Parse first-party dependencies. let (input, version) = package_version(input)?; - let version = match registry { - Some(registry) => PackageVersion::ThirdParty(ThirdPartyVersion { - version: version.trim().into(), + + // Parse local version specifier. + let (input, local_version) = opt(local_version)(input)?; + + let version = match (registry, local_version) { + (_, Some(_)) => PackageVersion::Unknown, + (Some(registry), _) => PackageVersion::ThirdParty(ThirdPartyVersion { + version: local_version.unwrap_or(version).trim().into(), registry: registry.into(), }), - None => PackageVersion::FirstParty(version.trim().into()), + (None, None) => PackageVersion::FirstParty(version.trim().into()), }; // Ensure line is empty after the dependency. @@ -185,6 +190,15 @@ fn package_version(input: &str) -> IResult<&str, &str> { recognize(many1(alt((alphanumeric1, tag(".")))))(input) } +/// Parse local version specifiers. +/// +/// https://packaging.python.org/en/latest/specifications/version-specifiers/#local-version-identifiers +fn local_version(input: &str) -> IResult<&str, &str> { + let (input, _) = tag("+")(input)?; + + recognize(many1(alt((alphanumeric1, tag(".")))))(input) +} + fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair(alphanumeric1, many0(alt((alphanumeric1, alt((tag("-"), tag("_"), tag("."))))))))( input, diff --git a/lockfile/src/python.rs b/lockfile/src/python.rs index 09b84b4f0..ec2cd25fa 100644 --- a/lockfile/src/python.rs +++ b/lockfile/src/python.rs @@ -238,7 +238,7 @@ mod tests { let pkgs = PyRequirements .parse(include_str!("../../tests/fixtures/requirements-locked.txt")) .unwrap(); - assert_eq!(pkgs.len(), 14); + assert_eq!(pkgs.len(), 15); let expected_pkgs = [ Package { @@ -317,6 +317,11 @@ mod tests { }), package_type: PackageType::PyPi, }, + Package { + name: "localversion".into(), + version: PackageVersion::Unknown, + package_type: PackageType::PyPi, + }, ]; for expected_pkg in expected_pkgs { diff --git a/tests/fixtures/requirements-locked.txt b/tests/fixtures/requirements-locked.txt index 07297db1c..ac347b86c 100644 --- a/tests/fixtures/requirements-locked.txt +++ b/tests/fixtures/requirements-locked.txt @@ -20,6 +20,8 @@ requests[security,tests]==2.28.1 werkzeug==2.9.2 ; python_version >= "3.7" and python_version < "3.12" +localversion==2.3.4+1.0.99.local + attr @ file:///tmp/attr numpy @ file:///tmp/testing/numpy-1.23.5-pp38-pypy38_pp73-win_amd64.whl diff --git a/tests/fixtures/requirements-unlocked.txt b/tests/fixtures/requirements-unlocked.txt index 4f075a6e0..1daaf904a 100644 --- a/tests/fixtures/requirements-unlocked.txt +++ b/tests/fixtures/requirements-unlocked.txt @@ -32,3 +32,4 @@ FooProject8 > 1.8.* FooProject9 > 2.0.*, !=2.1 FooProject10==1.2.3 --random-flag git+https://github.com/matiascodesal/git-for-pip-example.git@v1.0.0#egg=my-git-package +localversion >= 2.3.4+1.0.99.local