diff --git a/Cargo.lock b/Cargo.lock index 47d4dd78a..7ecec1f72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,6 +367,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bracoxide" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc0bcb5424e8e1f29c21a00f2d222df5e8e9779732ff03f840315d8fbac708e" + [[package]] name = "brownstone" version = "3.0.0" @@ -4655,6 +4661,7 @@ dependencies = [ name = "spk-workspace" version = "0.42.0" dependencies = [ + "bracoxide", "format_serde_error", "glob", "itertools 0.12.0", diff --git a/Cargo.toml b/Cargo.toml index 1ca8b2365..3683aa754 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ description = "SPK is a Package Manager for high-velocity software environments, [workspace.dependencies] arc-swap = "1.6.0" async-trait = "0.1" +bracoxide = "0.1.4" bytes = "1.5" cached = "0.48.1" chrono = { version = "0.4.34", features = ["serde"] } diff --git a/crates/spk-workspace/Cargo.toml b/crates/spk-workspace/Cargo.toml index dc3ebb7df..23c07a017 100644 --- a/crates/spk-workspace/Cargo.toml +++ b/crates/spk-workspace/Cargo.toml @@ -16,15 +16,16 @@ workspace = true sentry = ["spk-solve/sentry"] [dependencies] -serde = { workspace = true, features = ["derive"] } +bracoxide = { workspace = true } +format_serde_error = { workspace = true } glob = { workspace = true } itertools = { workspace = true } -spk-solve = { workspace = true } -thiserror = { workspace = true } miette = { workspace = true } +serde = { workspace = true, features = ["derive"] } serde_yaml = { workspace = true } spk-schema = { workspace = true } -format_serde_error = { workspace = true } +spk-solve = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] rstest = { workspace = true } diff --git a/crates/spk-workspace/src/file.rs b/crates/spk-workspace/src/file.rs index 9b14f67c7..a7d7a37b6 100644 --- a/crates/spk-workspace/src/file.rs +++ b/crates/spk-workspace/src/file.rs @@ -3,9 +3,13 @@ // https://github.com/spkenv/spk use std::path::Path; +use std::str::FromStr; +use bracoxide::tokenizer::TokenizationError; +use bracoxide::OxidizationError; use serde::Deserialize; use spk_schema::foundation::FromYaml; +use spk_schema::version::Version; use crate::error::LoadWorkspaceFileError; @@ -75,6 +79,7 @@ impl WorkspaceFile { #[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] pub struct RecipesItem { pub path: glob::Pattern, + pub versions: Vec, } impl<'de> serde::de::Deserialize<'de> for RecipesItem { @@ -96,7 +101,10 @@ impl<'de> serde::de::Deserialize<'de> for RecipesItem { E: serde::de::Error, { let path = glob::Pattern::new(v).map_err(serde::de::Error::custom)?; - Ok(RecipesItem { path }) + Ok(RecipesItem { + path, + versions: Vec::new(), + }) } fn visit_map(self, map: A) -> Result @@ -106,11 +114,42 @@ impl<'de> serde::de::Deserialize<'de> for RecipesItem { #[derive(Deserialize)] struct RawRecipeItem { path: String, + #[serde(default)] + versions: Vec, } let raw_recipe = RawRecipeItem::deserialize(serde::de::value::MapAccessDeserializer::new(map))?; - self.visit_str(&raw_recipe.path) + let mut base = self.visit_str(&raw_recipe.path)?; + for (i, version_expr) in raw_recipe.versions.into_iter().enumerate() { + let expand_result = bracoxide::bracoxidize(&version_expr); + let expanded = match expand_result { + Ok(expanded) => expanded, + Err(OxidizationError::TokenizationError(TokenizationError::NoBraces)) + | Err(OxidizationError::TokenizationError( + TokenizationError::EmptyContent, + )) + | Err(OxidizationError::TokenizationError( + TokenizationError::FormatNotSupported, + )) => { + vec![version_expr] + } + Err(err) => { + return Err(serde::de::Error::custom(format!( + "invalid brace expansion in position {i}: {err:?}" + ))) + } + }; + for version in expanded { + let parsed = Version::from_str(&version).map_err(|err| { + serde::de::Error::custom(format!( + "brace expansion in position {i} produced invalid version '{version}': {err}" + )) + })?; + base.versions.push(parsed); + } + } + Ok(base) } } diff --git a/workspace.spk.yml b/workspace.spk.yml index cc340e414..46bfe1271 100644 --- a/workspace.spk.yml +++ b/workspace.spk.yml @@ -1,4 +1,28 @@ api: v0/workspace recipes: - - packages/**.spk.yml + # collect all of the recipes in the workspace + - packages/**/*.spk.yaml + + # some recipes require additional information + # which can be augmented even if they were already + # collected above + + - path: packages/python/python2.spk.yaml + # here, we define the specific versions that can + # be build from a recipe + versions: [2.7.18] + + - path: packages/python/python3.spk.yaml + # we can use bash-style brace expansion to define + # ranges of versions that are supported + versions: + - '3.7.{0..17}' + - '3.8.{0..20}' + - '3.9.{0..21}' + - '3.10.{0..16}' + - '3.11.{0..11}' + - '3.12.{0..8}' + - '3.13.{0..1}' + +