diff --git a/crates/spk-cli/cmd-build/src/cmd_build_test/mod.rs b/crates/spk-cli/cmd-build/src/cmd_build_test/mod.rs index 98dd20fc63..bf546178cc 100644 --- a/crates/spk-cli/cmd-build/src/cmd_build_test/mod.rs +++ b/crates/spk-cli/cmd-build/src/cmd_build_test/mod.rs @@ -798,6 +798,56 @@ build: } } +#[rstest] +#[case::cli("cli")] +#[case::checks("checks")] +#[case::resolvo("resolvo")] +#[tokio::test] +async fn test_build_package_with_required_var_dependency( + tmpdir: tempfile::TempDir, + #[case] solver_to_run: &str, +) { + let _rt = spfs_runtime().await; + + build_package!( + tmpdir, + "mylib.spk.yaml", + br#" +api: v0/package +pkg: mylib/1.0.0 + +build: + options: + - var: namespace_style/major_minor + required: true + description: "The namespace style to use" + script: + - "true" +"#, + solver_to_run + ); + + try_build_package!( + tmpdir, + "mypkg.spk.yaml", + br#" +api: v0/package +pkg: mypkg/1.0.0 + +build: + options: + - pkg: mylib + - var: mylib.namespace_style/major_minor + description: "The namespace style to use" + script: + - "true" +"#, + solver_to_run + ) + .1 + .expect("Expected build of mypkg to succeed"); +} + #[rstest] #[case::cli("cli")] #[case::checks("checks")] diff --git a/crates/spk-schema/src/v0/recipe_spec.rs b/crates/spk-schema/src/v0/recipe_spec.rs index 47cb20cb1e..87062613bd 100644 --- a/crates/spk-schema/src/v0/recipe_spec.rs +++ b/crates/spk-schema/src/v0/recipe_spec.rs @@ -14,6 +14,7 @@ use spk_schema_foundation::IsDefault; use spk_schema_foundation::ident::{ AsVersionIdent, PinnedRequest, + PkgRequestOptionValue, PkgRequestOptions, RangeIdent, VersionIdent, @@ -208,6 +209,26 @@ impl Recipe for RecipeSpec { let options = self.resolve_options(variant)?; let build_digest = Build::BuildId(self.build_digest(variant)?); let mut requests = RequirementsList::::default(); + + // Collect namespaced var options keyed by their package namespace so + // they can be attached to the corresponding package request. + let mut pkg_var_options: HashMap = HashMap::new(); + for opt in opts.iter() { + if let Opt::Var(var_opt) = opt + && let Some(ns) = var_opt.var.namespace() + && let Some(value) = options.get(&var_opt.var) + && !value.is_empty() + { + pkg_var_options + .entry(ns.as_str().to_owned()) + .or_default() + .insert( + var_opt.var.clone(), + PkgRequestOptionValue::Complete(value.to_string()), + ); + } + } + for opt in opts { match opt { Opt::Pkg(opt) => { @@ -220,10 +241,11 @@ impl Recipe for RecipeSpec { // inject the default component for this context if needed req.pkg.components.insert(Component::default_for_build()); } + let pkg_options = pkg_var_options.remove(opt.pkg.as_str()).unwrap_or_default(); requests.insert_or_merge_with_options(RequestWithOptions::Pkg( PkgRequestWithOptions { pkg_request: req, - options: PkgRequestOptions::default(), + options: pkg_options, }, ))?; } diff --git a/crates/spk-solve/src/solvers/step/solver.rs b/crates/spk-solve/src/solvers/step/solver.rs index b84b56e7d1..a43e38f127 100644 --- a/crates/spk-solve/src/solvers/step/solver.rs +++ b/crates/spk-solve/src/solvers/step/solver.rs @@ -996,10 +996,27 @@ impl Solver { // The count is used as a fake version number to // distinguish which initial request is being checked in // each dummy package. + + // Translate any options on the request into build options + // for the dummy recipe so that the var values are carried + // through to the install requirements and the impossible + // request checker can see them. + let build_options: Vec = req + .options + .iter() + .map(|(name, value)| { + serde_json::json!({ + "var": format!("{}/{}", name, value.value()), + }) + }) + .collect(); + let recipe = try_recipe!({"pkg": format!("initialrequest/{}", count + 1), + "build": { + "options": build_options, + }, "install": { "requirements": [ - // TODO: include options here req.pkg_request, ] }