diff --git a/crates/spk-cli/cmd-env/src/cmd_env.rs b/crates/spk-cli/cmd-env/src/cmd_env.rs index e8d2756cdc..c4748de6ac 100644 --- a/crates/spk-cli/cmd-env/src/cmd_env.rs +++ b/crates/spk-cli/cmd-env/src/cmd_env.rs @@ -35,7 +35,7 @@ pub struct Env { pub formatter_settings: flags::DecisionFormatterSettings, /// The requests to resolve and run - #[clap(name = "REQUESTS")] + #[clap(name = "REQUESTS|REQUESTS_YAML_FILE")] pub requested: Vec, /// An optional command to run in the resolved environment. diff --git a/crates/spk-cli/cmd-explain/src/cmd_explain.rs b/crates/spk-cli/cmd-explain/src/cmd_explain.rs index 592c9d4035..cfce00d9e5 100644 --- a/crates/spk-cli/cmd-explain/src/cmd_explain.rs +++ b/crates/spk-cli/cmd-explain/src/cmd_explain.rs @@ -23,7 +23,7 @@ pub struct Explain { pub formatter_settings: flags::DecisionFormatterSettings, /// The requests to resolve - #[clap(name = "REQUESTS", required = true)] + #[clap(name = "REQUESTS|REQUESTS_YAML_FILE", required = true)] pub requested: Vec, // The following arguments were previously provided by the `runtime` field. diff --git a/crates/spk-cli/common/src/flags.rs b/crates/spk-cli/common/src/flags.rs index b335f44ca0..c0b7aae8a6 100644 --- a/crates/spk-cli/common/src/flags.rs +++ b/crates/spk-cli/common/src/flags.rs @@ -418,30 +418,70 @@ impl Requests { { let mut out = Vec::::new(); let options = options.get_options()?; + for r in requests.into_iter() { - let r = r.as_ref(); - if r.contains('@') { - let (recipe, _, stage, build_variant) = parse_stage_specifier(r, &options, repos) - .await - .wrap_err_with(|| format!("parsing {r} as a filename with stage specifier"))?; + let r: &str = r.as_ref(); - match stage { - TestStage::Sources => { - if build_variant.is_some() { - bail!("Source stage does not accept a build variant specifier") - } + // Is it a filepath to a package requests yaml file? + if r.ends_with(".yaml") { + //let filename: std::path::PathBuf = r.into(); + let reader = std::fs::File::open(r) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to open: {r}"))?; + let requests_from_file: Vec = serde_yaml::from_reader(reader) + .into_diagnostic() + .wrap_err_with(|| { + format!("Failed to parse as list of package requests: {r}") + })?; + for req in requests_from_file { + let new_requests = self.parse_non_file_request(&req, &options, repos).await?; + out.extend(new_requests); + } + continue; + } - let ident = recipe.ident().to_any(Some(Build::Source)); - out.push( - PkgRequest::from_ident_exact(ident, RequestedBy::CommandLine).into(), - ); + // It's not a filepath to a package requests yaml file + let reqs = self.parse_non_file_request(r, &options, repos).await?; + out.extend(reqs); + } + + Ok(out) + } + + async fn parse_non_file_request( + &self, + request: &str, + options: &OptionMap, + repos: &[Arc], + ) -> Result> { + // Parses a string that isn't a path to a request file into + // one or more requests. + let mut out = Vec::::new(); + + // If this a request with a '@' stage-specifier, it may result + // in multiple requests + if request.contains('@') { + let (recipe, _, stage, build_variant) = parse_stage_specifier(request, options, repos) + .await + .wrap_err_with(|| { + format!("parsing {request} as a filename with stage specifier") + })?; + + match stage { + TestStage::Sources => { + if build_variant.is_some() { + bail!("Source stage does not accept a build variant specifier") } - TestStage::Build => { - let requirements = match build_variant { - Some(VariantIndex(index)) => { - let default_variants = recipe.default_variants(&options); - let variant = + let ident = recipe.ident().to_any(Some(Build::Source)); + out.push(PkgRequest::from_ident_exact(ident, RequestedBy::CommandLine).into()); + } + + TestStage::Build => { + let requirements = match build_variant { + Some(VariantIndex(index)) => { + let default_variants = recipe.default_variants(options); + let variant = default_variants .iter() .skip(index) @@ -453,71 +493,72 @@ impl Requests { recipe.ident().format_ident() ))? .with_overrides(options.clone()); - recipe.get_build_requirements(&variant)? - } - None => recipe.get_build_requirements(&options)?, - }; - out.extend(requirements.into_owned()); - } - - TestStage::Install => { - if build_variant.is_some() { - bail!("Install stage does not accept a build variant specifier") + recipe.get_build_requirements(&variant)? } + None => recipe.get_build_requirements(&options)?, + }; + out.extend(requirements.into_owned()); + } - out.push( - PkgRequest::from_ident_exact( - recipe.ident().to_any(None), - RequestedBy::CommandLine, - ) - .into(), - ) + TestStage::Install => { + if build_variant.is_some() { + bail!("Install stage does not accept a build variant specifier") } - } - continue; - } - let value: serde_yaml::Value = serde_yaml::from_str(r) - .into_diagnostic() - .wrap_err("Request was not a valid yaml value")?; - let mut request_data = match value { - v @ serde_yaml::Value::String(_) => { - let mut mapping = serde_yaml::Mapping::with_capacity(1); - mapping.insert("pkg".into(), v); - mapping - } - serde_yaml::Value::Mapping(m) => m, - _ => { - bail!( - "Invalid request, expected either a string or a mapping, got: {:?}", - value + + out.push( + PkgRequest::from_ident_exact( + recipe.ident().to_any(None), + RequestedBy::CommandLine, + ) + .into(), ) } - }; - - let prerelease_policy_key = "prereleasePolicy".into(); - if self.pre && !request_data.contains_key(&prerelease_policy_key) { - request_data.insert(prerelease_policy_key, "IncludeAll".into()); } + return Ok(out); + } - let mut req = serde_yaml::from_value::(request_data.into()) - .into_diagnostic() - .wrap_err(format!("Failed to parse request {r}"))? - .into_pkg() - .ok_or_else(|| miette!("Expected a package request, got None"))?; - req.add_requester(RequestedBy::CommandLine); - - if req.pkg.components.is_empty() { - if req.pkg.is_source() { - req.pkg.components.insert(Component::Source); - } else { - req.pkg.components.insert(Component::default_for_run()); - } + // This is request without a '@' stage specifier + let value: serde_yaml::Value = serde_yaml::from_str(request) + .into_diagnostic() + .wrap_err("Request was not a valid yaml value")?; + let mut request_data = match value { + v @ serde_yaml::Value::String(_) => { + let mut mapping = serde_yaml::Mapping::with_capacity(1); + mapping.insert("pkg".into(), v); + mapping } - if req.required_compat.is_none() { - req.required_compat = Some(CompatRule::API); + serde_yaml::Value::Mapping(m) => m, + _ => { + bail!( + "Invalid request, expected either a string or a mapping, got: {:?}", + value + ) } - out.push(req.into()); + }; + + let prerelease_policy_key = "prereleasePolicy".into(); + if self.pre && !request_data.contains_key(&prerelease_policy_key) { + request_data.insert(prerelease_policy_key, "IncludeAll".into()); + } + + let mut req = serde_yaml::from_value::(request_data.into()) + .into_diagnostic() + .wrap_err(format!("Failed to parse request {request}"))? + .into_pkg() + .ok_or_else(|| miette!("Expected a package request, got None"))?; + req.add_requester(RequestedBy::CommandLine); + + if req.pkg.components.is_empty() { + if req.pkg.is_source() { + req.pkg.components.insert(Component::Source); + } else { + req.pkg.components.insert(Component::default_for_run()); + } + } + if req.required_compat.is_none() { + req.required_compat = Some(CompatRule::API); } + out.push(req.into()); Ok(out) } diff --git a/crates/spk-cli/group1/src/cmd_bake.rs b/crates/spk-cli/group1/src/cmd_bake.rs index b427595148..f1394dabfd 100644 --- a/crates/spk-cli/group1/src/cmd_bake.rs +++ b/crates/spk-cli/group1/src/cmd_bake.rs @@ -63,7 +63,7 @@ pub struct Bake { pub formatter_settings: flags::DecisionFormatterSettings, /// The requests to resolve and bake - #[clap(name = "REQUESTS")] + #[clap(name = "REQUESTS|REQUESTS_YAML_FILE")] pub requested: Vec, }