feat(wfe-yaml): wire rustlang step types and containerd integration tests
Add rustlang feature flag to wfe-yaml with support for all cargo and rustup step types (15 total), including cargo-doc-mdx. Schema additions: output_dir, package, features, all_features, no_default_features, release, profile, toolchain, extra_args, components, targets, default_toolchain fields on StepConfig. Integration tests for compiling all step types from YAML, and containerd-based end-to-end tests for running Rust toolchain inside containers from bare Debian images.
This commit is contained in:
@@ -13,6 +13,8 @@ use crate::executors::deno::{DenoConfig, DenoPermissions, DenoStep};
|
||||
use wfe_buildkit::{BuildkitConfig, BuildkitStep};
|
||||
#[cfg(feature = "containerd")]
|
||||
use wfe_containerd::{ContainerdConfig, ContainerdStep};
|
||||
#[cfg(feature = "rustlang")]
|
||||
use wfe_rustlang::{CargoCommand, CargoConfig, CargoStep, RustupCommand, RustupConfig, RustupStep};
|
||||
use wfe_core::primitives::sub_workflow::SubWorkflowStep;
|
||||
use wfe_core::models::condition::{ComparisonOp, FieldComparison, StepCondition};
|
||||
|
||||
@@ -454,6 +456,38 @@ fn build_step_config_and_factory(
|
||||
});
|
||||
Ok((key, value, factory))
|
||||
}
|
||||
#[cfg(feature = "rustlang")]
|
||||
"cargo-build" | "cargo-test" | "cargo-check" | "cargo-clippy" | "cargo-fmt"
|
||||
| "cargo-doc" | "cargo-publish" | "cargo-audit" | "cargo-deny" | "cargo-nextest"
|
||||
| "cargo-llvm-cov" | "cargo-doc-mdx" => {
|
||||
let config = build_cargo_config(step, step_type)?;
|
||||
let key = format!("wfe_yaml::cargo::{}", step.name);
|
||||
let value = serde_json::to_value(&config).map_err(|e| {
|
||||
YamlWorkflowError::Compilation(format!(
|
||||
"Failed to serialize cargo config: {e}"
|
||||
))
|
||||
})?;
|
||||
let config_clone = config.clone();
|
||||
let factory: StepFactory = Box::new(move || {
|
||||
Box::new(CargoStep::new(config_clone.clone())) as Box<dyn StepBody>
|
||||
});
|
||||
Ok((key, value, factory))
|
||||
}
|
||||
#[cfg(feature = "rustlang")]
|
||||
"rust-install" | "rustup-toolchain" | "rustup-component" | "rustup-target" => {
|
||||
let config = build_rustup_config(step, step_type)?;
|
||||
let key = format!("wfe_yaml::rustup::{}", step.name);
|
||||
let value = serde_json::to_value(&config).map_err(|e| {
|
||||
YamlWorkflowError::Compilation(format!(
|
||||
"Failed to serialize rustup config: {e}"
|
||||
))
|
||||
})?;
|
||||
let config_clone = config.clone();
|
||||
let factory: StepFactory = Box::new(move || {
|
||||
Box::new(RustupStep::new(config_clone.clone())) as Box<dyn StepBody>
|
||||
});
|
||||
Ok((key, value, factory))
|
||||
}
|
||||
"workflow" => {
|
||||
let config = step.config.as_ref().ok_or_else(|| {
|
||||
YamlWorkflowError::Compilation(format!(
|
||||
@@ -576,6 +610,88 @@ fn build_shell_config(step: &YamlStep) -> Result<ShellConfig, YamlWorkflowError>
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustlang")]
|
||||
fn build_cargo_config(
|
||||
step: &YamlStep,
|
||||
step_type: &str,
|
||||
) -> Result<CargoConfig, YamlWorkflowError> {
|
||||
let command = match step_type {
|
||||
"cargo-build" => CargoCommand::Build,
|
||||
"cargo-test" => CargoCommand::Test,
|
||||
"cargo-check" => CargoCommand::Check,
|
||||
"cargo-clippy" => CargoCommand::Clippy,
|
||||
"cargo-fmt" => CargoCommand::Fmt,
|
||||
"cargo-doc" => CargoCommand::Doc,
|
||||
"cargo-publish" => CargoCommand::Publish,
|
||||
"cargo-audit" => CargoCommand::Audit,
|
||||
"cargo-deny" => CargoCommand::Deny,
|
||||
"cargo-nextest" => CargoCommand::Nextest,
|
||||
"cargo-llvm-cov" => CargoCommand::LlvmCov,
|
||||
"cargo-doc-mdx" => CargoCommand::DocMdx,
|
||||
_ => {
|
||||
return Err(YamlWorkflowError::Compilation(format!(
|
||||
"Unknown cargo step type: '{step_type}'"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let config = step.config.as_ref();
|
||||
let timeout_ms = config
|
||||
.and_then(|c| c.timeout.as_ref())
|
||||
.and_then(|t| parse_duration_ms(t));
|
||||
|
||||
Ok(CargoConfig {
|
||||
command,
|
||||
toolchain: config.and_then(|c| c.toolchain.clone()),
|
||||
package: config.and_then(|c| c.package.clone()),
|
||||
features: config.map(|c| c.features.clone()).unwrap_or_default(),
|
||||
all_features: config.and_then(|c| c.all_features).unwrap_or(false),
|
||||
no_default_features: config.and_then(|c| c.no_default_features).unwrap_or(false),
|
||||
release: config.and_then(|c| c.release).unwrap_or(false),
|
||||
target: config.and_then(|c| c.target.clone()),
|
||||
profile: config.and_then(|c| c.profile.clone()),
|
||||
extra_args: config.map(|c| c.extra_args.clone()).unwrap_or_default(),
|
||||
env: config.map(|c| c.env.clone()).unwrap_or_default(),
|
||||
working_dir: config.and_then(|c| c.working_dir.clone()),
|
||||
timeout_ms,
|
||||
output_dir: config.and_then(|c| c.output_dir.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustlang")]
|
||||
fn build_rustup_config(
|
||||
step: &YamlStep,
|
||||
step_type: &str,
|
||||
) -> Result<RustupConfig, YamlWorkflowError> {
|
||||
let command = match step_type {
|
||||
"rust-install" => RustupCommand::Install,
|
||||
"rustup-toolchain" => RustupCommand::ToolchainInstall,
|
||||
"rustup-component" => RustupCommand::ComponentAdd,
|
||||
"rustup-target" => RustupCommand::TargetAdd,
|
||||
_ => {
|
||||
return Err(YamlWorkflowError::Compilation(format!(
|
||||
"Unknown rustup step type: '{step_type}'"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let config = step.config.as_ref();
|
||||
let timeout_ms = config
|
||||
.and_then(|c| c.timeout.as_ref())
|
||||
.and_then(|t| parse_duration_ms(t));
|
||||
|
||||
Ok(RustupConfig {
|
||||
command,
|
||||
toolchain: config.and_then(|c| c.toolchain.clone()),
|
||||
components: config.map(|c| c.components.clone()).unwrap_or_default(),
|
||||
targets: config.map(|c| c.targets.clone()).unwrap_or_default(),
|
||||
profile: config.and_then(|c| c.profile.clone()),
|
||||
default_toolchain: config.and_then(|c| c.default_toolchain.clone()),
|
||||
extra_args: config.map(|c| c.extra_args.clone()).unwrap_or_default(),
|
||||
timeout_ms,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_duration_ms(s: &str) -> Option<u64> {
|
||||
let s = s.trim();
|
||||
// Check "ms" before "s" since strip_suffix('s') would also match "500ms"
|
||||
|
||||
@@ -164,6 +164,39 @@ pub struct StepConfig {
|
||||
pub containerd_addr: Option<String>,
|
||||
/// CLI binary name for containerd steps: "nerdctl" (default) or "docker".
|
||||
pub cli: Option<String>,
|
||||
// Cargo fields
|
||||
/// Target package for cargo steps (`-p`).
|
||||
pub package: Option<String>,
|
||||
/// Features to enable for cargo steps.
|
||||
#[serde(default)]
|
||||
pub features: Vec<String>,
|
||||
/// Enable all features for cargo steps.
|
||||
#[serde(default)]
|
||||
pub all_features: Option<bool>,
|
||||
/// Disable default features for cargo steps.
|
||||
#[serde(default)]
|
||||
pub no_default_features: Option<bool>,
|
||||
/// Build in release mode for cargo steps.
|
||||
#[serde(default)]
|
||||
pub release: Option<bool>,
|
||||
/// Build profile for cargo steps (`--profile`).
|
||||
pub profile: Option<String>,
|
||||
/// Rust toolchain override for cargo steps (e.g. "nightly").
|
||||
pub toolchain: Option<String>,
|
||||
/// Additional arguments for cargo/rustup steps.
|
||||
#[serde(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
/// Output directory for generated files (e.g., MDX docs).
|
||||
pub output_dir: Option<String>,
|
||||
// Rustup fields
|
||||
/// Components to add for rustup steps (e.g. ["clippy", "rustfmt"]).
|
||||
#[serde(default)]
|
||||
pub components: Vec<String>,
|
||||
/// Compilation targets to add for rustup steps (e.g. ["wasm32-unknown-unknown"]).
|
||||
#[serde(default)]
|
||||
pub targets: Vec<String>,
|
||||
/// Default toolchain for rust-install steps.
|
||||
pub default_toolchain: Option<String>,
|
||||
// Workflow (sub-workflow) fields
|
||||
/// Child workflow ID (for `type: workflow` steps).
|
||||
#[serde(rename = "workflow")]
|
||||
|
||||
Reference in New Issue
Block a user