Skip to content

Commit

Permalink
feat: improve version detection
Browse files Browse the repository at this point in the history
  • Loading branch information
louib committed Nov 19, 2023
1 parent 693ad89 commit e0eda3e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/cyclone_dx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ pub fn dump_derivation(derivation_path: &str, package_node: &crate::nix::Package
component_builder.type_("application".to_string());
// I'm assuming here that if a package has been installed by Nix, it was required.
component_builder.scope("required".to_string());
component_builder.purl(package_node.get_purl().unwrap().to_string());
component_builder.purl(package_node.get_purl().to_string());
if let Some(v) = package_node.get_version() {
component_builder.version(v.to_string());
}
Expand Down
68 changes: 53 additions & 15 deletions src/nix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,33 @@ impl Derivation {
response.push(PrettyPrintLine::new("unknown derivation?", depth + 1));
response
}

pub fn get_version(&self) -> Option<String> {
let pname = match self.env.get("pname") {
Some(n) => n,
None => return None,
};
let name = match self.env.get("name") {
Some(n) => n,
None => return None,
};
if name.contains(pname) {
let package_name_prefix = pname.to_string() + "-";
return Some(name.replace(&package_name_prefix, ""));
}
for url in self.get_urls() {
if let Some(commit_sha) = crate::utils::get_git_sha_from_archive_url(&url) {
return Some(commit_sha);
}
if let Some(version) = crate::utils::get_semver_from_archive_url(&url) {
return Some(version);
}
}
if let Some(commit_sha) = self.env.get("rev").cloned() {
return Some(commit_sha);
}
None
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -550,7 +577,15 @@ impl PackageNode {

for source in &self.sources {
if let Some(source_name) = source.get_name() {
return Some(source_name.to_string());
if source_name != "source" {
return Some(source_name.to_string());
}
}
}

if let Some(name) = self.main_derivation.get_name() {
if name != "source" {
return Some(name.to_string());
}
}

Expand All @@ -573,19 +608,10 @@ impl PackageNode {
}
}

for url in self.main_derivation.get_urls() {
if let Some(version) = crate::utils::get_semver_from_archive_url(&url) {
return Some(version);
}
if let Some(commit_sha) = self.main_derivation.env.get("rev").cloned() {
return Some(commit_sha);
}
}

return None;
}

pub fn get_purl(&self) -> Option<PackageURL> {
pub fn get_purl(&self) -> PackageURL {
let mut name: Option<String> = self.get_name();
if let Some(n) = &name {
log::debug!("Found package name from source: {}", &n);
Expand All @@ -597,9 +623,20 @@ impl PackageNode {
name = Some("unknown".to_string());
}

if name == Some("source".to_string()) {
log::trace!("{}", self.to_json().unwrap());
}
// FIXME not sure what to do with these yet.
if name == Some("raw".to_string()) {
log::trace!("{}", self.to_json().unwrap());
}

let mut version: Option<String> = self.get_version();
if let Some(v) = &version {
log::debug!("Found package version: {}", &v);
if version.is_none() {
version = self.main_derivation.get_version();
}
if version.is_none() {
log::trace!("{}", self.to_json().unwrap());
}

// FIXME this cannot use the nix scope, which does not actually exist.
Expand All @@ -608,6 +645,7 @@ impl PackageNode {
let scheme = "generic";
// TODO detect the scheme using the url.
// if url.starts_with("https://crates.io") {}
// https://crates.io/api/v1/crates/project-name/1.0.2/download
// if url.starts_with("https://bitbucket.org") {}
// if url.starts_with("https://registry.npmjs.org") {}
// if url.starts_with("https://pypi.python.org") {}
Expand All @@ -619,7 +657,7 @@ impl PackageNode {
package_url.host = name.unwrap_or("".to_string());
package_url.version = version;
package_url.scheme = scheme.to_string();
return Some(package_url);
return package_url;
}

pub fn to_json(&self) -> Result<String, String> {
Expand All @@ -638,7 +676,7 @@ impl PackageNode {
return lines;
}

lines.push(PrettyPrintLine::new(self.get_purl().unwrap().to_string(), depth));
lines.push(PrettyPrintLine::new(self.get_purl().to_string(), depth));

if !display_options.print_only_purl {
if let Some(p) = &self.package {
Expand Down
24 changes: 24 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ lazy_static! {
static ref SEMVER_REGEX: Regex = Regex::new(r"([0-9]+.[0-9]+.[0-9]+)(-[0-9a-zA-Z_]+)?").unwrap();
}

lazy_static! {
static ref GIT_SHA_REGEX: Regex = Regex::new(r"([0-9a-fA-F]{40})").unwrap();
}

lazy_static! {
static ref PROJECT_NAME_AND_SEMVER_REGEX: Regex =
Regex::new(r"([0-9a-zA-Z_-]+)-([0-9]+.[0-9]+.[0-9]+)(-[0-9a-zA-Z_]+)?").unwrap();
Expand Down Expand Up @@ -232,6 +236,17 @@ pub fn get_semver_from_archive_url(archive_url: &str) -> Option<String> {
return Some(captured_groups[1].to_string());
}

pub fn get_git_sha_from_archive_url(archive_url: &str) -> Option<String> {
let captured_groups = match GIT_SHA_REGEX.captures(archive_url) {
Some(g) => g,
None => return None,
};
if captured_groups.len() == 0 {
return None;
}
return Some(captured_groups[0].to_string());
}

pub fn get_project_name_from_archive_url(archive_url: &str) -> Option<String> {
let archive_filename = archive_url.split("/").last().unwrap();
if let Some(g) = PROJECT_NAME_AND_SEMVER_REGEX.captures(archive_filename) {
Expand Down Expand Up @@ -335,6 +350,15 @@ mod tests {
);
}

#[test]
pub fn test_get_git_sha_from_archive() {
let sha = crate::utils::get_git_sha_from_archive_url(
"https://raw.githubusercontent.com/NixOS/nixos-artwork/766f10e0c93cb1236a85925a089d861b52ed2905/wallpapers/nix-wallpaper-simple-blue.png"
);
assert!(sha.is_some());
assert_eq!(sha.unwrap(), "766f10e0c93cb1236a85925a089d861b52ed2905");
}

#[test]
pub fn test_get_semver_from_archive() {
let version = crate::utils::get_semver_from_archive_url(
Expand Down

0 comments on commit e0eda3e

Please sign in to comment.