Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

### Added

- Support for C#'s `packages.*.config` lockfile type

## 7.1.5 - 2024-11-26

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ impl<'a> TestExtension<'a> {
}

#[cfg(feature = "extensions")]
impl<'a> Drop for TestExtension<'a> {
impl Drop for TestExtension<'_> {
fn drop(&mut self) {
self.test_cli.run(["extension", "uninstall", "test-ext"]).success();
fs::remove_dir_all(&self.extension_path).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/phylum_analyze.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Usage: phylum analyze [OPTIONS] [DEPENDENCY_FILE]...

`-t`, `--type` `<TYPE>`
&emsp; Dependency file type used for all lockfiles (default: auto)
&emsp; Accepted values: `npm`, `yarn`, `pnpm`, `gem`, `pip`, `poetry`, `pipenv`, `mvn`, `gradle`, `msbuild`, `nugetlock`, `gomod`, `go`, `cargo`, `spdx`, `cyclonedx`, `auto`
&emsp; Accepted values: `npm`, `yarn`, `pnpm`, `gem`, `pip`, `poetry`, `pipenv`, `mvn`, `gradle`, `msbuild`, `nugetlock`, `nugetconfig`, `gomod`, `go`, `cargo`, `spdx`, `cyclonedx`, `auto`

`--skip-sandbox`
&emsp; Run lockfile generation without sandbox protection
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/phylum_init.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Usage: phylum init [OPTIONS] [PROJECT_NAME]

`-t`, `--type` `<TYPE>`
&emsp; Dependency file type used for all lockfiles (default: auto)
&emsp; Accepted values: `npm`, `yarn`, `pnpm`, `gem`, `pip`, `poetry`, `pipenv`, `mvn`, `gradle`, `msbuild`, `nugetlock`, `gomod`, `go`, `cargo`, `spdx`, `cyclonedx`, `auto`
&emsp; Accepted values: `npm`, `yarn`, `pnpm`, `gem`, `pip`, `poetry`, `pipenv`, `mvn`, `gradle`, `msbuild`, `nugetlock`, `nugetconfig`, `gomod`, `go`, `cargo`, `spdx`, `cyclonedx`, `auto`

`-f`, `--force`
&emsp; Overwrite existing configurations without confirmation
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/phylum_parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Usage: phylum parse [OPTIONS] [DEPENDENCY_FILE]...

`-t`, `--type` `<TYPE>`
&emsp; Dependency file type used for all lockfiles (default: auto)
&emsp; Accepted values: `npm`, `yarn`, `pnpm`, `gem`, `pip`, `poetry`, `pipenv`, `mvn`, `gradle`, `msbuild`, `nugetlock`, `gomod`, `go`, `cargo`, `spdx`, `cyclonedx`, `auto`
&emsp; Accepted values: `npm`, `yarn`, `pnpm`, `gem`, `pip`, `poetry`, `pipenv`, `mvn`, `gradle`, `msbuild`, `nugetlock`, `nugetconfig`, `gomod`, `go`, `cargo`, `spdx`, `cyclonedx`, `auto`

`--skip-sandbox`
&emsp; Run lockfile generation without sandbox protection
Expand Down
1 change: 1 addition & 0 deletions docs/supported_lockfiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The Phylum CLI supports processing many different lockfiles:
| `gem` | `Gemfile.lock` |
| `msbuild` | `*.csproj` |
| `nugetlock` | `packages.lock.json` <br /> `packages.*.lock.json` |
| `nugetconfig` | `packages.config` <br /> `packages.*.config` |
| `mvn` | `effective-pom.xml` |
| `gradle` | `gradle.lockfile` <br /> `gradle/dependency-locks/*.lockfile` |
| `go` | `go.sum` |
Expand Down
89 changes: 89 additions & 0 deletions lockfile/src/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,61 @@ impl Parse for CSProj {
}
}

pub struct PackagesConfig;

impl Parse for PackagesConfig {
/// Parses `packages.*.config` files into a [`Vec`] of packages.
fn parse(&self, data: &str) -> anyhow::Result<Vec<Package>> {
let data = data.trim_start_matches(UTF8_BOM);
let parsed: PackagesConfigXml = quick_xml::de::from_str(data)?;
Ok(parsed.into())
}

fn is_path_lockfile(&self, path: &Path) -> bool {
let file_name = match path.file_name().and_then(|f| f.to_str()) {
Some(file_name) => file_name,
None => return false,
};

// Accept both `packages.config` and `packages.<project_name>.config`.
file_name.starts_with("packages.") && file_name.ends_with(".config")
}

fn is_path_manifest(&self, _path: &Path) -> bool {
false
}
}

/// XML format of the `packages.*.config` file.
#[derive(Deserialize)]
struct PackagesConfigXml {
#[serde(rename = "package", default)]
packages: Vec<PackagesConfigXmlPackage>,
}

impl From<PackagesConfigXml> for Vec<Package> {
fn from(packages_config: PackagesConfigXml) -> Self {
packages_config
.packages
.into_iter()
.map(|package| {
let version =
package.version.map_or(PackageVersion::Unknown, PackageVersion::FirstParty);
Package { version, name: package.id, package_type: PackageType::Nuget }
})
.collect()
}
}

/// Package entry in a `packages.*.config` file.
#[derive(Deserialize)]
struct PackagesConfigXmlPackage {
#[serde(alias = "@id")]
id: String,
#[serde(alias = "@version")]
version: Option<String>,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -302,4 +357,38 @@ mod tests {
assert_eq!(pkgs[1].name, "NUnit3TestAdapter");
assert_eq!(pkgs[1].version, PackageVersion::FirstParty("3.13.0".into()));
}

#[test]
fn packages_config() {
let pkgs =
PackagesConfig.parse(include_str!("../../tests/fixtures/packages.config")).unwrap();
assert_eq!(pkgs.len(), 4);

let expected_pkgs = [
Package {
name: "AddressParser".into(),
version: PackageVersion::FirstParty("0.0.20".into()),
package_type: PackageType::Nuget,
},
Package {
name: "JetBrains.ReSharper.SDK".into(),
version: PackageVersion::FirstParty("8.2.921-EAP".into()),
package_type: PackageType::Nuget,
},
Package {
name: "boost".into(),
version: PackageVersion::FirstParty("1.78.0".into()),
package_type: PackageType::Nuget,
},
Package {
name: "noversion".into(),
version: PackageVersion::Unknown,
package_type: PackageType::Nuget,
},
];

for expected_pkg in expected_pkgs {
assert!(pkgs.contains(&expected_pkg), "missing package {expected_pkg:?}");
}
}
}
67 changes: 33 additions & 34 deletions lockfile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use thiserror::Error;
use walkdir::WalkDir;

pub use crate::cargo::Cargo;
pub use crate::csharp::{CSProj, PackagesLock};
pub use crate::csharp::{CSProj, PackagesConfig, PackagesLock};
pub use crate::cyclonedx::CycloneDX;
pub use crate::golang::{GoMod, GoSum};
pub use crate::java::{GradleLock, Pom};
Expand Down Expand Up @@ -61,6 +61,7 @@ pub enum LockfileFormat {
#[serde(alias = "nuget")]
Msbuild,
NugetLock,
NugetConfig,
GoMod,
Go,
Cargo,
Expand Down Expand Up @@ -103,6 +104,7 @@ impl LockfileFormat {
LockfileFormat::Gradle => "gradle",
LockfileFormat::Msbuild => "msbuild",
LockfileFormat::NugetLock => "nugetlock",
LockfileFormat::NugetConfig => "nugetconfig",
LockfileFormat::GoMod => "gomod",
LockfileFormat::Go => "go",
LockfileFormat::Cargo => "cargo",
Expand All @@ -125,6 +127,7 @@ impl LockfileFormat {
LockfileFormat::Gradle => &GradleLock,
LockfileFormat::Msbuild => &CSProj,
LockfileFormat::NugetLock => &PackagesLock,
LockfileFormat::NugetConfig => &PackagesConfig,
LockfileFormat::GoMod => &GoMod,
LockfileFormat::Go => &GoSum,
LockfileFormat::Cargo => &Cargo,
Expand All @@ -134,44 +137,32 @@ impl LockfileFormat {
}

/// Iterate over all supported lockfile formats.
pub fn iter() -> LockfileFormatIter {
LockfileFormatIter(0)
}
}

/// An iterator of all supported lockfile formats.
pub struct LockfileFormatIter(u8);

impl Iterator for LockfileFormatIter {
type Item = LockfileFormat;

fn next(&mut self) -> Option<Self::Item> {
pub fn iter() -> impl Iterator<Item = LockfileFormat> {
// NOTE: Without explicit override, the lockfile generator will always pick the
// first matching format for the manifest. To ensure best possible support,
// common formats should be returned **before** less common ones (i.e. NPM
// before Yarn).
const FORMATS: &[LockfileFormat] = &[
LockfileFormat::Npm,
LockfileFormat::Yarn,
LockfileFormat::Pnpm,
LockfileFormat::Gem,
LockfileFormat::Pip,
LockfileFormat::Poetry,
LockfileFormat::Pipenv,
LockfileFormat::Maven,
LockfileFormat::Gradle,
LockfileFormat::Msbuild,
LockfileFormat::NugetLock,
LockfileFormat::NugetConfig,
LockfileFormat::GoMod,
LockfileFormat::Go,
LockfileFormat::Cargo,
LockfileFormat::Spdx,
LockfileFormat::CycloneDX,
];

let item = match self.0 {
0 => LockfileFormat::Npm,
1 => LockfileFormat::Yarn,
2 => LockfileFormat::Pnpm,
3 => LockfileFormat::Gem,
4 => LockfileFormat::Pip,
5 => LockfileFormat::Poetry,
6 => LockfileFormat::Pipenv,
7 => LockfileFormat::Maven,
8 => LockfileFormat::Gradle,
9 => LockfileFormat::Msbuild,
10 => LockfileFormat::NugetLock,
11 => LockfileFormat::GoMod,
12 => LockfileFormat::Go,
13 => LockfileFormat::Cargo,
14 => LockfileFormat::Spdx,
15 => LockfileFormat::CycloneDX,
_ => return None,
};
self.0 += 1;
Some(item)
FORMATS.iter().copied()
}
}

Expand Down Expand Up @@ -519,6 +510,9 @@ mod tests {
("pnpm-lock.yaml", LockfileFormat::Pnpm),
("sample.csproj", LockfileFormat::Msbuild),
("packages.lock.json", LockfileFormat::NugetLock),
("packages.project.lock.json", LockfileFormat::NugetLock),
("packages.config", LockfileFormat::NugetConfig),
("packages.project.config", LockfileFormat::NugetConfig),
("gradle.lockfile", LockfileFormat::Gradle),
("default.lockfile", LockfileFormat::Gradle),
("effective-pom.xml", LockfileFormat::Maven),
Expand All @@ -528,7 +522,9 @@ mod tests {
("go.sum", LockfileFormat::Go),
("Cargo.lock", LockfileFormat::Cargo),
(".spdx.json", LockfileFormat::Spdx),
("file.spdx.json", LockfileFormat::Spdx),
(".spdx.yaml", LockfileFormat::Spdx),
("file.spdx.yaml", LockfileFormat::Spdx),
("bom.json", LockfileFormat::CycloneDX),
("bom.xml", LockfileFormat::CycloneDX),
];
Expand All @@ -555,6 +551,7 @@ mod tests {
("nuget", LockfileFormat::Msbuild),
("msbuild", LockfileFormat::Msbuild),
("nugetlock", LockfileFormat::NugetLock),
("nugetconfig", LockfileFormat::NugetConfig),
("gomod", LockfileFormat::GoMod),
("go", LockfileFormat::Go),
("cargo", LockfileFormat::Cargo),
Expand Down Expand Up @@ -585,6 +582,7 @@ mod tests {
("gradle", LockfileFormat::Gradle),
("msbuild", LockfileFormat::Msbuild),
("nugetlock", LockfileFormat::NugetLock),
("nugetconfig", LockfileFormat::NugetConfig),
("gomod", LockfileFormat::GoMod),
("go", LockfileFormat::Go),
("cargo", LockfileFormat::Cargo),
Expand Down Expand Up @@ -629,6 +627,7 @@ mod tests {
(LockfileFormat::Gradle, 1),
(LockfileFormat::Msbuild, 2),
(LockfileFormat::NugetLock, 1),
(LockfileFormat::NugetConfig, 1),
(LockfileFormat::GoMod, 1),
(LockfileFormat::Go, 1),
(LockfileFormat::Cargo, 3),
Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AddressParser" version="0.0.20" targetFramework="net471" />
<package id="JetBrains.ReSharper.SDK" version="8.2.921-EAP" targetFramework="net35" />
<package id="boost" version="1.78.0" targetFramework="native" developmentDependency="true" />
<package id="noversion" />
</packages>
Loading