Skip to content

Commit

Permalink
Adds support to spk commands to take path to yaml files of package re…
Browse files Browse the repository at this point in the history
…quests

This allows a larger number of request to be passed to spk bake, env
and explain than would fit on a single command line. The yaml file
must contain a list of package requests.

Signed-off-by: David Gilligan-Cook <dcook@imageworks.com>
  • Loading branch information
dcookspi committed Sep 24, 2024
1 parent 2f76165 commit c7f609f
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 77 deletions.
2 changes: 1 addition & 1 deletion crates/spk-cli/cmd-env/src/cmd_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,

/// An optional command to run in the resolved environment.
Expand Down
2 changes: 1 addition & 1 deletion crates/spk-cli/cmd-explain/src/cmd_explain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,

// The following arguments were previously provided by the `runtime` field.
Expand Down
189 changes: 115 additions & 74 deletions crates/spk-cli/common/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,30 +418,70 @@ impl Requests {
{
let mut out = Vec::<Request>::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<String> = 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<storage::RepositoryHandle>],
) -> Result<Vec<Request>> {
// Parses a string that isn't a path to a request file into
// one or more requests.
let mut out = Vec::<Request>::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)
Expand All @@ -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>(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>(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)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/spk-cli/group1/src/cmd_bake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

Expand Down

0 comments on commit c7f609f

Please sign in to comment.