diff --git a/Cargo.lock b/Cargo.lock index 49d3ff9b..c546f31c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,7 @@ dependencies = [ "human-panic", "protoc-bin-vendored", "reqwest", + "semver", "serde", "serde_typename", "tar", @@ -1368,6 +1369,15 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +dependencies = [ + "serde", +] + [[package]] name = "serde" version = "1.0.171" diff --git a/Cargo.toml b/Cargo.toml index 73185fcb..081a3400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ protoc-bin-vendored = "3.0.0" reqwest = "0.11" serde = { version = "1", features = ["derive"] } serde_typename = "0.1" +semver = { version = "1", features = ["serde"] } tar = "0.4" tokio = { version = "1", features = ["full", "tracing"] } toml = "0.7" diff --git a/src/lib.rs b/src/lib.rs index 7ac663c3..6e618b65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ pub fn build(language: Language) -> eyre::Result<()> { ) })?; - if pkg.version == dependency.manifest.version { + if dependency.manifest.version.matches(&pkg.version) { continue; } } diff --git a/src/main.rs b/src/main.rs index 023d0c97..5b980d48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -142,6 +142,7 @@ mod cmd { }; use eyre::{ensure, Context, ContextCompat}; use futures::future::try_join_all; + use semver::{Version, VersionReq}; /// Initializes the project pub async fn init(r#type: Option<(PackageType, Option)>) -> eyre::Result<()> { @@ -163,7 +164,7 @@ mod cmd { manifest.package = Some(PackageManifest { r#type, name, - version: "0.0.1".to_owned(), + version: Version::new(0, 0, 1), description: None, }); } @@ -200,20 +201,15 @@ mod cmd { .parse::() .wrap_err_with(|| format!("Invalid package id supplied: {package}"))?; - ensure!( - version - .chars() - .all(|c| c.is_alphanumeric() || c == '.' || c == '-'), - "Version specifications must be in the format ..-" - ); + let version = version + .parse::() + .wrap_err_with(|| format!("Invalid version requirement supplied: {package}"))?; let mut manifest = Manifest::read().await?; - manifest.dependencies.push(Dependency::new( - repository.to_owned(), - package, - version.to_owned(), - )); + manifest + .dependencies + .push(Dependency::new(repository.to_owned(), package, version)); manifest.write().await } diff --git a/src/manifest.rs b/src/manifest.rs index 41025230..9c4f15c4 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -1,6 +1,7 @@ // (c) Copyright 2023 Helsing GmbH. All rights reserved. use eyre::Context; +use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fmt}; use tokio::fs; @@ -100,13 +101,13 @@ pub struct PackageManifest { /// Name of the package pub name: PackageId, /// Version of the package - pub version: String, + pub version: Version, /// Description of the api package pub description: Option, } /// Represents a single project dependency -#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)] pub struct Dependency { /// Package name of this dependency pub package: PackageId, @@ -116,7 +117,7 @@ pub struct Dependency { impl Dependency { /// Creates a new dependency - pub fn new(repository: String, package: PackageId, version: String) -> Self { + pub fn new(repository: String, package: PackageId, version: VersionReq) -> Self { Self { package, manifest: DependencyManifest { @@ -138,10 +139,10 @@ impl fmt::Display for Dependency { } /// Manifest format for dependencies -#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)] pub struct DependencyManifest { /// Version requirement in the helsing format, currently only supports pinning - pub version: String, + pub version: VersionReq, /// Artifactory repository to pull dependency from pub repository: String, } diff --git a/src/package.rs b/src/package.rs index 2de4c368..9941cde2 100644 --- a/src/package.rs +++ b/src/package.rs @@ -105,7 +105,7 @@ impl PackageStore { ))?; eyre::ensure!( - existing.version == dependency.manifest.version, + dependency.manifest.version.matches(&existing.version), "A dependency of your project requires {}@{} which collides with {}@{} required by {}", existing.name, existing.version, diff --git a/src/registry/artifactory.rs b/src/registry/artifactory.rs index 9a283363..ff708145 100644 --- a/src/registry/artifactory.rs +++ b/src/registry/artifactory.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use eyre::{ensure, Context}; +use eyre::{ensure, Context, ContextCompat}; use serde::{Deserialize, Serialize}; use url::Url; @@ -40,6 +40,36 @@ impl Artifactory { impl Registry for Artifactory { /// Downloads a package from artifactory async fn download(&self, dependency: Dependency) -> eyre::Result { + ensure!( + dependency.manifest.version.comparators.len() == 1, + "{} uses unsupported semver comparators", + dependency.package + ); + + let version = dependency + .manifest + .version + .comparators + .first() + .wrap_err("internal error")?; + + ensure!( + version.op == semver::Op::Exact && version.minor.is_some() && version.patch.is_some(), + "artifactory only support pinned dependencies" + ); + + let version = format!( + "{}.{}.{}{}", + version.major, + version.minor.wrap_err("internal error")?, + version.patch.wrap_err("internal error")?, + if version.pre.is_empty() { + "".to_owned() + } else { + format!("-{}", version.pre) + } + ); + let artifact_uri: Url = { let mut url = self.0.url.clone(); @@ -49,7 +79,7 @@ impl Registry for Artifactory { dependency.manifest.repository, dependency.package, dependency.package, - dependency.manifest.version + version )); url