diff --git a/Cargo.lock b/Cargo.lock index 728ff27..1cc8399 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,6 +651,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -942,6 +951,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn", @@ -1585,6 +1595,7 @@ dependencies = [ "serde_json", "sha1", "sha2", + "smart-default", "tar", "tempfile", "thiserror 2.0.18", @@ -3647,6 +3658,17 @@ dependencies = [ "serde", ] +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.6.3" diff --git a/Cargo.toml b/Cargo.toml index 7244bb8..9d9610a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,8 @@ semver = { version = "1", features = ["serde"] } uuid = { version = "1", features = ["serde", "v4"] } chrono = { version = "0.4", features = ["serde"] } thiserror = "2" -derive_more = { version = "1", default-features = false, features = ["deref", "from", "display"] } +derive_more = { version = "1", default-features = false, features = ["full"] } +smart-default = "0.7" tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.7", features = ["rt"] } extism = "1" diff --git a/crates/hm-plugin-docker/src/image_name.rs b/crates/hm-plugin-docker/src/image_name.rs index f48c58e..637b43d 100644 --- a/crates/hm-plugin-docker/src/image_name.rs +++ b/crates/hm-plugin-docker/src/image_name.rs @@ -25,10 +25,10 @@ pub(crate) fn resolve_image( parent_snapshot: Option<&SnapshotRef>, ) -> String { if let Some(tag) = hit_tag { - return tag.0.clone(); + return tag.to_string(); } if let Some(snap) = parent_snapshot { - return snap.0.clone(); + return snap.to_string(); } if let Some(image) = &step.image { return image.clone(); diff --git a/crates/hm-plugin-docker/src/lib.rs b/crates/hm-plugin-docker/src/lib.rs index 4bc4dbe..43fcbbc 100644 --- a/crates/hm-plugin-docker/src/lib.rs +++ b/crates/hm-plugin-docker/src/lib.rs @@ -134,7 +134,7 @@ fn run_step(input: ExecutorInput) -> Result { }); match host::commit(DockerCommitArgs { container_id: cid.clone(), - tag: target_tag.0.clone(), + tag: target_tag.to_string(), }) { Ok(_) => Some(target_tag), Err(e) => { diff --git a/crates/hm-plugin-protocol/src/events.rs b/crates/hm-plugin-protocol/src/events.rs index 99b0788..cd5d935 100644 --- a/crates/hm-plugin-protocol/src/events.rs +++ b/crates/hm-plugin-protocol/src/events.rs @@ -9,14 +9,14 @@ use uuid::Uuid; use crate::executor::SnapshotRef; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(rename_all = "snake_case")] pub enum StdStream { Stdout, Stderr, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum BuildEvent { BuildStart { diff --git a/crates/hm-plugin-protocol/src/executor.rs b/crates/hm-plugin-protocol/src/executor.rs index bc86727..4b03d02 100644 --- a/crates/hm-plugin-protocol/src/executor.rs +++ b/crates/hm-plugin-protocol/src/executor.rs @@ -36,7 +36,7 @@ pub struct ArtifactRef { /// Host-decided cache outcome. The executor honours this; it does /// not re-decide. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum CacheDecision { /// Boot from `tag`; skip running `cmd`. diff --git a/crates/hm-plugin-protocol/src/hook.rs b/crates/hm-plugin-protocol/src/hook.rs index 2ab98e2..4cf6d95 100644 --- a/crates/hm-plugin-protocol/src/hook.rs +++ b/crates/hm-plugin-protocol/src/hook.rs @@ -14,7 +14,7 @@ pub struct HookEvent { pub phase: HookPhase, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(rename_all = "snake_case")] pub enum HookPhase { /// May return [`HookOutcome::Abort`] to fail the build. @@ -23,7 +23,7 @@ pub enum HookPhase { After, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum HookOutcome { /// Continue the build. @@ -37,7 +37,7 @@ pub enum HookOutcome { /// /// The manifest declares *what* events the plugin wants, not the per-event /// payload. Kept in this file so plugin authors only import one module. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(rename_all = "snake_case")] pub enum HookEventKind { BuildStart, diff --git a/crates/hm-plugin-protocol/src/host_abi.rs b/crates/hm-plugin-protocol/src/host_abi.rs index f69c333..d4fe10d 100644 --- a/crates/hm-plugin-protocol/src/host_abi.rs +++ b/crates/hm-plugin-protocol/src/host_abi.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::executor::ArchiveId; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(rename_all = "snake_case")] pub enum Level { Trace, @@ -19,7 +19,7 @@ pub enum Level { Error, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(rename_all = "snake_case")] pub enum KvScope { /// Per-plugin, persistent across builds. Stored in diff --git a/crates/hm-plugin-protocol/src/ir.rs b/crates/hm-plugin-protocol/src/ir.rs index 8446ba9..0cd11a3 100644 --- a/crates/hm-plugin-protocol/src/ir.rs +++ b/crates/hm-plugin-protocol/src/ir.rs @@ -24,7 +24,7 @@ pub struct Pipeline { pub steps: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(tag = "type", rename_all = "snake_case")] pub enum Step { Command(Box), diff --git a/crates/hm-plugin-protocol/src/manifest.rs b/crates/hm-plugin-protocol/src/manifest.rs index ba279f6..a7a9eb9 100644 --- a/crates/hm-plugin-protocol/src/manifest.rs +++ b/crates/hm-plugin-protocol/src/manifest.rs @@ -43,7 +43,7 @@ pub struct PluginManifest { pub allowed_hosts: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveJsonSchema, derive_more::IsVariant)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum Capability { Subcommand(SubcommandSpec), diff --git a/crates/hm/Cargo.toml b/crates/hm/Cargo.toml index 45ec7da..ce60a36 100644 --- a/crates/hm/Cargo.toml +++ b/crates/hm/Cargo.toml @@ -71,6 +71,7 @@ hm-plugin-protocol = { workspace = true } hm-util = { workspace = true } schemars = { workspace = true } semver = { workspace = true } +smart-default = { workspace = true } once_cell = "1" hex = "0.4" diff --git a/crates/hm/src/cli/plugin.rs b/crates/hm/src/cli/plugin.rs index eeaeb97..b232bee 100644 --- a/crates/hm/src/cli/plugin.rs +++ b/crates/hm/src/cli/plugin.rs @@ -50,10 +50,7 @@ pub async fn run(cmd: PluginCommand) -> Result<()> { #[allow(clippy::unused_async)] async fn list() -> Result<()> { - let reg = PluginRegistry::load(RegistryConfig { - auto_discover: true, - ..Default::default() - })?; + let reg = PluginRegistry::load(RegistryConfig::default())?; if reg.manifests().count() == 0 { println!("No plugins installed."); println!(); @@ -78,10 +75,7 @@ async fn list() -> Result<()> { #[allow(clippy::unused_async)] async fn info(name: &str) -> Result<()> { - let reg = PluginRegistry::load(RegistryConfig { - auto_discover: true, - ..Default::default() - })?; + let reg = PluginRegistry::load(RegistryConfig::default())?; let m = reg .manifests() .find(|m| m.name == name) diff --git a/crates/hm/src/cli/version.rs b/crates/hm/src/cli/version.rs index 5f07d44..5297478 100644 --- a/crates/hm/src/cli/version.rs +++ b/crates/hm/src/cli/version.rs @@ -10,10 +10,7 @@ use crate::plugin::{PluginRegistry, RegistryConfig}; /// /// Returns an error if the plugin registry cannot be loaded. pub async fn run() -> Result<()> { - let reg = PluginRegistry::load(RegistryConfig { - auto_discover: true, - ..Default::default() - })?; + let reg = PluginRegistry::load(RegistryConfig::default())?; println!("hm {}", env!("CARGO_PKG_VERSION")); println!("plugin api version: {HM_PLUGIN_API_VERSION}"); let count = reg.manifests().count(); diff --git a/crates/hm/src/orchestrator/docker_host_fns.rs b/crates/hm/src/orchestrator/docker_host_fns.rs index 0f873b0..46f6b66 100644 --- a/crates/hm/src/orchestrator/docker_host_fns.rs +++ b/crates/hm/src/orchestrator/docker_host_fns.rs @@ -122,7 +122,7 @@ pub(crate) async fn exec_impl(args: DockerExecArgs) -> Result { // Emit StepLog events for each line written; the writer below // forwards bytes into the event bus tagged with the current // thread-local step_id set by the scheduler. - let mut writer = StepLogWriter::new(); + let mut writer = StepLogWriter::default(); // Future doing the exec; we race it against cancellation. let cancel = s.cancel.clone(); @@ -184,17 +184,13 @@ pub(crate) async fn stop_remove_impl(container_id: String) { /// Streams bytes from a Docker exec into per-line `StepLog` events on /// the event bus. Buffers partial lines until a `\n` arrives. +#[derive(smart_default::SmartDefault)] struct StepLogWriter { + #[default(Vec::with_capacity(8192))] buf: Vec, } impl StepLogWriter { - fn new() -> Self { - Self { - buf: Vec::with_capacity(8192), - } - } - fn flush_line(line: &[u8]) { let Some(state) = current() else { return }; let Some(step_id) = crate::plugin::host_fns::current_step_id() else { diff --git a/crates/hm/src/orchestrator/output_subscriber.rs b/crates/hm/src/orchestrator/output_subscriber.rs index b19c5f4..eefe584 100644 --- a/crates/hm/src/orchestrator/output_subscriber.rs +++ b/crates/hm/src/orchestrator/output_subscriber.rs @@ -29,7 +29,6 @@ use std::sync::Arc; use anyhow::Result; -use hm_plugin_protocol::BuildEvent; use tokio::sync::Mutex; use tokio::sync::broadcast::error::RecvError; @@ -63,20 +62,20 @@ pub fn spawn( let Some(&idx) = reg.output_formatter_index.get(&format_name) else { // No plugin for this format; CLI parser // should have caught this. Drain silently. - if matches!(event, BuildEvent::BuildEnd { .. }) { + if event.is_build_end() { return Ok(()); } continue; }; let Some(p) = reg.get(idx) else { - if matches!(event, BuildEvent::BuildEnd { .. }) { + if event.is_build_end() { return Ok(()); } continue; }; p }; - let is_end = matches!(event, BuildEvent::BuildEnd { .. }); + let is_end = event.is_build_end(); // Log-and-continue on formatter failures: a broken // output plugin shouldn't fail the build. let _: Result<()> = plugin.call_capability("hm_output_on_event", &event).await; diff --git a/crates/hm/src/plugin/registry.rs b/crates/hm/src/plugin/registry.rs index ecb0a8f..2afad9d 100644 --- a/crates/hm/src/plugin/registry.rs +++ b/crates/hm/src/plugin/registry.rs @@ -27,10 +27,11 @@ use super::manifest::{ManifestError, validate_standalone}; use super::paths; use crate::error::HmError; -#[derive(Debug, Default)] +#[derive(Debug, smart_default::SmartDefault)] pub struct RegistryConfig { /// If `false`, skip discovery and only registers explicitly added /// plugins. Used by integration tests. + #[default = true] pub auto_discover: bool, /// Extra plugin paths to load (in addition to discovery). Used by /// tests to load fixture plugins.