Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workspaces Part II #1161

Open
wants to merge 4 commits into
base: workspaces
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions crates/spk-cli/cmd-make-recipe/src/cmd_make_recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ impl Run for MakeRecipe {
let options = self.options.get_options()?;
let workspace = self.workspace.load_or_default()?;

let template = match self.package.as_ref() {
let configured = match self.package.as_ref() {
None => workspace.default_package_template(),
Some(p) => workspace.find_package_template(p),
}
.must_be_found();

if let Some(name) = template.name() {
if let Some(name) = configured.template.name() {
tracing::info!("rendering template for {name}");
} else {
tracing::info!("rendering template without a name");
Expand All @@ -62,15 +62,15 @@ impl Run for MakeRecipe {
let data = spk_schema::TemplateData::new(&options);
tracing::debug!("full template data: {data:#?}");
let rendered = spk_schema_tera::render_template(
template.file_path().to_string_lossy(),
template.source(),
configured.template.file_path().to_string_lossy(),
configured.template.source(),
&data,
)
.into_diagnostic()
.wrap_err("Failed to render template")?;
print!("{rendered}");

match template.render(&options) {
match configured.template.render(&options) {
Err(err) => {
tracing::error!("This template did not render into a valid spec {err}");
Ok(1)
Expand Down
26 changes: 15 additions & 11 deletions crates/spk-cli/common/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,12 +379,12 @@ impl Requests {
let path = std::path::Path::new(package);
if path.is_file() {
let workspace = self.workspace.load_or_default()?;
let template = workspace.find_package_template(&package).must_be_found();
let rendered_data = template.render(options)?;
let configured = workspace.find_package_template(package).must_be_found();
let rendered_data = configured.template.render(options)?;
let recipe = rendered_data.into_recipe().wrap_err_with(|| {
format!(
"{filename} was expected to contain a recipe",
filename = template.file_path().to_string_lossy()
filename = configured.template.file_path().to_string_lossy()
)
})?;
idents.push(recipe.ident().to_any_ident(None));
Expand Down Expand Up @@ -738,6 +738,10 @@ impl std::str::FromStr for PackageSpecifier {
#[derive(Args, Default, Clone)]
pub struct Packages {
/// The package names or yaml spec files to operate on
///
/// Package requests may also come with a version when multiple
/// versions might be found in the local workspace or configured
/// repositories.
#[clap(name = "PKG|SPEC_FILE")]
pub packages: Vec<PackageSpecifier>,

Expand Down Expand Up @@ -821,20 +825,20 @@ where
None => workspace.default_package_template(),
Some(package_name) => workspace.find_package_template(package_name),
};
let template = match from_workspace {
let configured = match from_workspace {
FindPackageTemplateResult::Found(template) => template,
res @ FindPackageTemplateResult::MultipleTemplateFiles(_) => {
// must_be_found() will exit the program when called on MultipleTemplateFiles
res.must_be_found();
unreachable!()
}
FindPackageTemplateResult::NoTemplateFiles | FindPackageTemplateResult::NotFound(_) => {
FindPackageTemplateResult::NoTemplateFiles | FindPackageTemplateResult::NotFound(..) => {
// If couldn't find a template file, maybe there's an
// existing package/version that's been published
match package_name.map(AsRef::as_ref) {
Some(name) if std::path::Path::new(name).is_file() => {
tracing::debug!(?name, "Loading anonymous template file into workspace...");
workspace.load_template_file(name)?.clone()
workspace.load_template_file(name)?
}
Some(name) => {
tracing::debug!("Unable to find package file: {}", name);
Expand Down Expand Up @@ -885,17 +889,17 @@ where
}
}
};
let found = template.render(options).wrap_err_with(|| {
let found = configured.template.render(options).wrap_err_with(|| {
format!(
"{filename} was expected to contain a valid spk yaml data file",
filename = template.file_path().to_string_lossy()
filename = configured.template.file_path().to_string_lossy()
)
})?;
tracing::debug!(
"Rendered template from the data in {:?}",
template.file_path()
"Rendered configured.template from the data in {:?}",
configured.template.file_path()
);
Ok((found, template.file_path().to_owned()))
Ok((found, configured.template.file_path().to_owned()))
}

#[derive(Args, Clone)]
Expand Down
6 changes: 3 additions & 3 deletions crates/spk-cli/group4/src/cmd_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,16 @@ impl View {
workspace: &spk_workspace::Workspace,
show_variants_with_tests: bool,
) -> Result<i32> {
let template = match self.package.as_ref() {
let configured = match self.package.as_ref() {
None => workspace.default_package_template(),
Some(name) => workspace.find_package_template(name),
}
.must_be_found();
let rendered_data = template.render(options)?;
let rendered_data = configured.template.render(options)?;
let recipe = rendered_data.into_recipe().wrap_err_with(|| {
format!(
"{filename} was expected to contain a recipe",
filename = template.file_path().to_string_lossy()
filename = configured.template.file_path().to_string_lossy()
)
})?;

Expand Down
10 changes: 8 additions & 2 deletions crates/spk-schema/crates/foundation/src/version_range/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,8 +576,14 @@ pub struct LowestSpecifiedRange {
impl LowestSpecifiedRange {
pub const REQUIRED_NUMBER_OF_DIGITS: usize = 2;

pub fn new(specified: usize, base: Version) -> Self {
Self { specified, base }
pub fn new(mut base: Version) -> Self {
while base.parts.len() < Self::REQUIRED_NUMBER_OF_DIGITS {
base.parts.push(0);
}
Self {
specified: base.parts.len(),
base,
}
}
}

Expand Down
26 changes: 22 additions & 4 deletions crates/spk-schema/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://github.com/spkenv/spk

use std::borrow::Cow;
use std::collections::HashSet;
use std::io::Read;
use std::path::Path;
use std::str::FromStr;
Expand Down Expand Up @@ -136,23 +137,39 @@ macro_rules! spec {

/// A generic, structured data object that can be turned into a recipe
/// when provided with the necessary option values
#[derive(Debug)]
pub struct SpecTemplate {
name: Option<PkgNameBuf>,
versions: HashSet<Version>,
file_path: std::path::PathBuf,
template: String,
template: Arc<str>,
}

impl SpecTemplate {
/// The complete source string for this template
pub fn source(&self) -> &str {
&self.template
}
}

impl SpecTemplate {
/// The name of the item that this template will create.
pub fn name(&self) -> Option<&PkgNameBuf> {
self.name.as_ref()
}

/// The versions that are available to create with this template.
///
/// An empty set does not signify no versions, but rather that
/// nothing has been specified or discerned.
pub fn versions(&self) -> &HashSet<Version> {
&self.versions
}

/// Clear and reset the versions that are available to create
/// with this template.
pub fn set_versions(&mut self, versions: impl IntoIterator<Item = Version>) {
self.versions.clear();
self.versions.extend(versions);
}
}

impl Template for SpecTemplate {
Expand Down Expand Up @@ -251,7 +268,8 @@ impl TemplateExt for SpecTemplate {
Ok(Self {
file_path,
name,
template,
versions: Default::default(),
template: template.into(),
})
}
}
Expand Down
6 changes: 4 additions & 2 deletions crates/spk-schema/src/spec_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ sources:
let tpl = SpecTemplate {
name: Some(PkgName::new("my-package").unwrap().to_owned()),
file_path: "my-package.spk.yaml".into(),
template: SPEC.to_string(),
versions: Default::default(),
template: SPEC.into(),
};
let options = option_map! {"version" => "1.0.0"};
let err = tpl
Expand All @@ -347,7 +348,8 @@ fn test_template_namespace_options() {
let tpl = SpecTemplate {
name: Some(PkgName::new("my-package").unwrap().to_owned()),
file_path: "my-package.spk.yaml".into(),
template: SPEC.to_string(),
versions: Default::default(),
template: SPEC.into(),
};
let options = option_map! {"namespace.version" => "1.0.0"};
let rendered_data = tpl
Expand Down
45 changes: 21 additions & 24 deletions crates/spk-schema/src/version_range_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,30 +263,27 @@ fn arb_lowest_specified_range_from_version(
0..=(*version.parts.get(parts_to_generate - 1).unwrap()),
)
.prop_map(|(version, parts_to_generate, last_element_value)| {
VersionRange::LowestSpecified(LowestSpecifiedRange::new(
parts_to_generate,
Version {
parts: version
.parts
.iter()
.take(parts_to_generate)
.enumerate()
.map(|(index, num)| {
if index == parts_to_generate - 1 {
last_element_value
} else {
*num
}
})
.collect::<Vec<_>>()
.into(),
// Retain pre and post from original version because
// if the original has pre it might be smaller than
// the smallest value we generated without it.
pre: version.pre,
post: version.post,
},
))
VersionRange::LowestSpecified(LowestSpecifiedRange::new(Version {
parts: version
.parts
.iter()
.take(parts_to_generate)
.enumerate()
.map(|(index, num)| {
if index == parts_to_generate - 1 {
last_element_value
} else {
*num
}
})
.collect::<Vec<_>>()
.into(),
// Retain pre and post from original version because
// if the original has pre it might be smaller than
// the smallest value we generated without it.
pre: version.pre,
post: version.post,
}))
})
},
)
Expand Down
1 change: 1 addition & 0 deletions crates/spk-workspace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ tracing = { workspace = true }
rstest = { workspace = true }
serde_json = { workspace = true }
tempfile = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
62 changes: 45 additions & 17 deletions crates/spk-workspace/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
// https://github.com/spkenv/spk

use std::collections::HashSet;
use std::collections::HashMap;

use crate::error;

#[derive(Default)]
pub struct WorkspaceBuilder {
spec_files: HashSet<std::path::PathBuf>,
root: Option<std::path::PathBuf>,
spec_files: HashMap<std::path::PathBuf, crate::file::TemplateConfig>,
}

impl WorkspaceBuilder {
Expand All @@ -22,8 +23,9 @@ impl WorkspaceBuilder {
self,
dir: impl AsRef<std::path::Path>,
) -> Result<Self, error::FromPathError> {
let file = crate::file::WorkspaceFile::discover(dir)?;
self.load_from_file(file)
let (file, root) = crate::file::WorkspaceFile::discover(dir)?;
self.with_root(root)
.load_from_file(file)
.map_err(error::FromPathError::from)
}

Expand All @@ -32,37 +34,63 @@ impl WorkspaceBuilder {
self,
file: crate::file::WorkspaceFile,
) -> Result<Self, error::FromFileError> {
file.recipes.iter().try_fold(self, |builder, pattern| {
builder.with_glob_pattern(pattern.path.as_str())
})
file.recipes
.iter()
.try_fold(self, |builder, item| builder.with_recipes_item(item))
}

/// Specify the root directory for the workspace.
///
/// This is the path that will be used to resolve all
/// relative paths and relative glob patterns.
pub fn with_root(mut self, root: impl Into<std::path::PathBuf>) -> Self {
self.root = Some(root.into());
self
}

/// Add all recipe files matching a glob pattern to the workspace.
///
/// If the provided pattern is relative, it will be relative to the
/// current working directory.
pub fn with_glob_pattern<S: AsRef<str>>(
pub fn with_recipes_item(
mut self,
pattern: S,
item: &crate::file::RecipesItem,
) -> Result<Self, error::FromFileError> {
let mut glob_results = glob::glob(pattern.as_ref())?;
let with_root = self.root.as_deref().map(|p| p.join(item.path.as_str()));
let pattern = with_root
.as_deref()
.and_then(|p| p.to_str())
.unwrap_or(item.path.as_str());
let mut glob_results = glob::glob(pattern)?;
while let Some(path) = glob_results.next().transpose()? {
self = self.with_recipe_file(path);
self.spec_files
.entry(path)
.or_default()
.update(item.config.clone());
}

Ok(self)
}

/// Add a recipe file to the workspace.
pub fn with_recipe_file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.spec_files.insert(path.into());
self
/// Add all recipe files matching a glob pattern to the workspace.
///
/// If the provided pattern is relative, it will be relative to the
/// current working directory. All configuration for this path will be
/// left as the defaults unless already set.
pub fn with_glob_pattern<S: AsRef<str>>(
self,
pattern: S,
) -> Result<Self, error::FromFileError> {
self.with_recipes_item(&crate::file::RecipesItem {
path: glob::Pattern::new(pattern.as_ref())?,
config: Default::default(),
})
}

pub fn build(self) -> Result<super::Workspace, error::BuildError> {
let mut workspace = super::Workspace::default();
for file in self.spec_files {
workspace.load_template_file(file)?;
for (file, config) in self.spec_files {
workspace.load_template_file_with_config(file, config)?;
}
Ok(workspace)
}
Expand Down
Loading
Loading