From 2f6e33adf9a6772c790af85dae3f96c8ce677ddc Mon Sep 17 00:00:00 2001 From: Nick DiZazzo Date: Thu, 11 Jun 2026 23:45:28 -0400 Subject: [PATCH 1/3] feature(version): normalize version markers for different build types --- Cargo.lock | 9 + Cargo.toml | 2 + crates/mesh-llm-build-info/Cargo.toml | 16 + crates/mesh-llm-build-info/README.md | 16 + crates/mesh-llm-build-info/build.rs | 3 + crates/mesh-llm-build-info/src/lib.rs | 75 +++ crates/mesh-llm-cli/Cargo.toml | 1 + crates/mesh-llm-cli/src/parser.rs | 2 +- crates/mesh-llm-commands/Cargo.toml | 1 + crates/mesh-llm-commands/src/benchmark.rs | 2 +- crates/mesh-llm-commands/src/update.rs | 2 +- crates/mesh-llm-host-runtime/Cargo.toml | 1 + crates/mesh-llm-host-runtime/src/api/mod.rs | 4 +- crates/mesh-llm-host-runtime/src/api/tests.rs | 22 +- crates/mesh-llm-host-runtime/src/lib.rs | 4 +- .../mesh-llm-host-runtime/src/plugin/mcp.rs | 2 +- .../mesh-llm-host-runtime/src/runtime/mod.rs | 6 +- .../src/system/native_runtime.rs | 538 +++++++++++++++++- crates/mesh-llm-runtime-install/Cargo.toml | 3 +- crates/mesh-llm-runtime-install/src/lib.rs | 177 +++++- crates/mesh-llm-system/Cargo.toml | 1 + crates/mesh-llm-system/src/autoupdate.rs | 29 +- scripts/affected-crates.sh | 1 + scripts/build-linux.sh | 46 ++ scripts/build-mac.sh | 46 ++ scripts/build-release.sh | 45 ++ scripts/build-windows.ps1 | 61 ++ scripts/plan-clippy-batches.sh | 2 + scripts/publish-crates.sh | 5 + tools/xtask/src/main.rs | 4 + 30 files changed, 1077 insertions(+), 49 deletions(-) create mode 100644 crates/mesh-llm-build-info/Cargo.toml create mode 100644 crates/mesh-llm-build-info/README.md create mode 100644 crates/mesh-llm-build-info/build.rs create mode 100644 crates/mesh-llm-build-info/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3b98d14157..6f0786bbd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3802,12 +3802,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "mesh-llm-build-info" +version = "0.68.0" + [[package]] name = "mesh-llm-cli" version = "0.68.0" dependencies = [ "anyhow", "clap", + "mesh-llm-build-info", "mesh-llm-events", "serde", ] @@ -3854,6 +3859,7 @@ dependencies = [ "hf-hub", "iroh", "json5", + "mesh-llm-build-info", "mesh-llm-cli", "mesh-llm-identity", "mesh-llm-native-runtime", @@ -3988,6 +3994,7 @@ dependencies = [ "libc", "mdns-sd", "mesh-llm-api-server", + "mesh-llm-build-info", "mesh-llm-client", "mesh-llm-config", "mesh-llm-events", @@ -4168,6 +4175,7 @@ dependencies = [ "flate2", "futures-util", "hex", + "mesh-llm-build-info", "mesh-llm-hardware-profile", "mesh-llm-native-runtime", "reqwest 0.12.28", @@ -4216,6 +4224,7 @@ dependencies = [ "dirs", "hex", "libc", + "mesh-llm-build-info", "mesh-llm-gpu-bench", "reqwest 0.12.28", "semver", diff --git a/Cargo.toml b/Cargo.toml index dee586b7cf..d85880df07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/mesh-llm-commands", "crates/mesh-llm-config", "crates/mesh-llm-events", + "crates/mesh-llm-build-info", "crates/mesh-llm-gpu-bench", "crates/mesh-llm-host-runtime", "crates/mesh-llm-hardware-profile", @@ -69,6 +70,7 @@ version = "0.68.0" anyhow = "1" blake3 = "1" clap = { version = "4", features = ["derive"] } +mesh-llm-build-info = { path = "crates/mesh-llm-build-info", version = "0.68.0" } mesh-llm-skills = { path = "crates/mesh-llm-skills", version = "0.68.0" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/crates/mesh-llm-build-info/Cargo.toml b/crates/mesh-llm-build-info/Cargo.toml new file mode 100644 index 0000000000..9f7fa62a46 --- /dev/null +++ b/crates/mesh-llm-build-info/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mesh-llm-build-info" +edition.workspace = true +license.workspace = true +version.workspace = true +description = "Build and release version constants for mesh-llm" +repository = "https://github.com/Mesh-LLM/mesh-llm" +homepage = "https://github.com/Mesh-LLM/mesh-llm" +readme = "README.md" +keywords = ["llm", "mesh", "version"] +categories = ["development-tools"] + +build = "build.rs" + +[lints] +workspace = true diff --git a/crates/mesh-llm-build-info/README.md b/crates/mesh-llm-build-info/README.md new file mode 100644 index 0000000000..e167ad3d1b --- /dev/null +++ b/crates/mesh-llm-build-info/README.md @@ -0,0 +1,16 @@ +# mesh-llm-build-info + +Shared build and release version constants for Mesh LLM. + +This crate is intentionally dependency-free. It lets build scripts stamp a +source build with a SHA-bearing display version while preserving the package +release version for compatibility checks, cache identity, and release metadata. + +## API Shape + +- `BUILD_VERSION` is the stamped display version when + `MESH_LLM_BUILD_VERSION` is set at compile time, otherwise the package + version. +- `RELEASE_VERSION` is always the plain Cargo package version. +- `is_sha_build(version)` recognizes source-build metadata of the form + `+g` and `+g.dirty`. diff --git a/crates/mesh-llm-build-info/build.rs b/crates/mesh-llm-build-info/build.rs new file mode 100644 index 0000000000..f5ce7be1ee --- /dev/null +++ b/crates/mesh-llm-build-info/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-env-changed=MESH_LLM_BUILD_VERSION"); +} diff --git a/crates/mesh-llm-build-info/src/lib.rs b/crates/mesh-llm-build-info/src/lib.rs new file mode 100644 index 0000000000..8d8572b8fe --- /dev/null +++ b/crates/mesh-llm-build-info/src/lib.rs @@ -0,0 +1,75 @@ +pub const BUILD_VERSION: &str = match option_env!("MESH_LLM_BUILD_VERSION") { + Some(version) => version, + None => env!("CARGO_PKG_VERSION"), +}; + +pub const RELEASE_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn is_sha_build(version: &str) -> bool { + let Some((_, metadata)) = version.split_once('+') else { + return false; + }; + + let sha = if let Some(sha) = metadata.strip_suffix(".dirty") { + sha + } else { + metadata + }; + + let Some(hex) = sha.strip_prefix('g') else { + return false; + }; + + hex.len() >= 6 && hex.bytes().all(|byte| byte.is_ascii_hexdigit()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build_version_uses_override_when_present() { + let expected = option_env!("MESH_LLM_BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")); + assert_eq!(BUILD_VERSION, expected); + } + + #[test] + fn release_version_is_plain_package_version() { + assert_eq!(RELEASE_VERSION, env!("CARGO_PKG_VERSION")); + } + + #[test] + fn recognizes_clean_sha_build() { + assert!(is_sha_build("0.68.0+gABCDEF")); + } + + #[test] + fn recognizes_dirty_sha_build() { + assert!(is_sha_build("0.68.0+gABCDEF.dirty")); + } + + #[test] + fn recognizes_lowercase_sha_build() { + assert!(is_sha_build("0.68.0+gabcdef")); + } + + #[test] + fn rejects_malformed_metadata() { + assert!(!is_sha_build("0.68.0+gABCDEF.dirty.extra")); + } + + #[test] + fn rejects_missing_plus() { + assert!(!is_sha_build("0.68.0gABCDEF")); + } + + #[test] + fn rejects_short_sha() { + assert!(!is_sha_build("0.68.0+gABCD")); + } + + #[test] + fn rejects_non_hex_sha() { + assert!(!is_sha_build("0.68.0+gABCDEX")); + } +} diff --git a/crates/mesh-llm-cli/Cargo.toml b/crates/mesh-llm-cli/Cargo.toml index ed517bc517..77c4d6da5a 100644 --- a/crates/mesh-llm-cli/Cargo.toml +++ b/crates/mesh-llm-cli/Cargo.toml @@ -16,5 +16,6 @@ workspace = true [dependencies] anyhow.workspace = true clap.workspace = true +mesh-llm-build-info.workspace = true mesh-llm-events = { path = "../mesh-llm-events", version = "0.68.0" } serde.workspace = true diff --git a/crates/mesh-llm-cli/src/parser.rs b/crates/mesh-llm-cli/src/parser.rs index 350f20e8e4..2f05943862 100644 --- a/crates/mesh-llm-cli/src/parser.rs +++ b/crates/mesh-llm-cli/src/parser.rs @@ -392,7 +392,7 @@ impl MeshGuardrailCliMode { #[derive(Parser, Debug)] #[command( name = "mesh-llm", - version = env!("CARGO_PKG_VERSION"), + version = mesh_llm_build_info::BUILD_VERSION, about = "Pool GPUs over the internet for LLM inference", after_help = "Preferred runtime entrypoints:\n mesh-llm serve\n mesh-llm serve --model Qwen3-8B-Q4_K_M\n mesh-llm client --auto\n mesh-llm gpus\n\n`mesh-llm serve` loads startup models from ~/.mesh-llm/config.toml.\nRun with --help-advanced for all options.\n\nExternal backends (vLLM, TGI, Ollama):\n Install the plugin:\n mesh-llm plugins install openai-endpoint\n Add to ~/.mesh-llm/config.toml:\n [[plugin]]\n name = \"openai-endpoint\"\n url = \"http://gpu-box:8000/v1\"\n Then: mesh-llm serve (or: mesh-llm client for client-only mode)\n\nFlash-MoE SSD backend:\n Install the plugin:\n mesh-llm plugins install flash-moe\n Add [[plugin]] name = \"flash-moe\" with url or plugin-owned args.\n Then: mesh-llm serve (or: mesh-llm client for client-only mode)" )] diff --git a/crates/mesh-llm-commands/Cargo.toml b/crates/mesh-llm-commands/Cargo.toml index 2b81287ca2..1712fbc7b7 100644 --- a/crates/mesh-llm-commands/Cargo.toml +++ b/crates/mesh-llm-commands/Cargo.toml @@ -22,6 +22,7 @@ hf_hub = { package = "hf-hub", version = "1.0.0-rc.1", default-features = false, hex = "0.4.3" iroh = "1.0.0-rc.0" json5 = "1.3.1" +mesh-llm-build-info.workspace = true mesh-llm-cli = { path = "../mesh-llm-cli", version = "0.68.0" } mesh-llm-native-runtime = { path = "../mesh-llm-native-runtime", version = "0.68.0" } mesh-llm-plugin-manager = { path = "../mesh-llm-plugin-manager", version = "0.68.0" } diff --git a/crates/mesh-llm-commands/src/benchmark.rs b/crates/mesh-llm-commands/src/benchmark.rs index ef8c579c85..4659e8aba5 100644 --- a/crates/mesh-llm-commands/src/benchmark.rs +++ b/crates/mesh-llm-commands/src/benchmark.rs @@ -16,7 +16,7 @@ pub async fn dispatch_benchmark_command(command: &BenchmarkCommand) -> Result<() limit: *limit, max_tokens: *max_tokens, output: output.clone(), - user_agent_version: env!("CARGO_PKG_VERSION"), + user_agent_version: mesh_llm_build_info::BUILD_VERSION, }; benchmark_prompts::import_prompt_corpus(args).await } diff --git a/crates/mesh-llm-commands/src/update.rs b/crates/mesh-llm-commands/src/update.rs index 34afdeb14b..8b7a09f909 100644 --- a/crates/mesh-llm-commands/src/update.rs +++ b/crates/mesh-llm-commands/src/update.rs @@ -19,7 +19,7 @@ pub async fn run_update(cli: &Cli) -> Result<()> { flavor, detect_flavor, requested_version, - current_version: env!("CARGO_PKG_VERSION"), + current_version: mesh_llm_build_info::BUILD_VERSION, }) .await } diff --git a/crates/mesh-llm-host-runtime/Cargo.toml b/crates/mesh-llm-host-runtime/Cargo.toml index cdfe0343bc..b4d5a5807c 100644 --- a/crates/mesh-llm-host-runtime/Cargo.toml +++ b/crates/mesh-llm-host-runtime/Cargo.toml @@ -28,6 +28,7 @@ workspace = true [dependencies] bytes = "1" +mesh-llm-build-info.workspace = true mesh-mixture-of-agents = { path = "../mesh-mixture-of-agents", version = "0.68.0" } mesh-llm-config = { path = "../mesh-llm-config", version = "0.68.0" } mesh-llm-events = { path = "../mesh-llm-events", version = "0.68.0" } diff --git a/crates/mesh-llm-host-runtime/src/api/mod.rs b/crates/mesh-llm-host-runtime/src/api/mod.rs index 85480e69cd..b83cd25124 100644 --- a/crates/mesh-llm-host-runtime/src/api/mod.rs +++ b/crates/mesh-llm-host-runtime/src/api/mod.rs @@ -92,7 +92,7 @@ use crate::network::proxy; #[cfg(test)] use crate::runtime::wakeable::{WakeableInventoryEntry, WakeableState}; -const MESH_LLM_VERSION: &str = crate::VERSION; +const MESH_LLM_BUILD_VERSION: &str = crate::BUILD_VERSION; async fn external_inference_models(plugin_manager: &plugin::PluginManager) -> Vec { plugin_manager @@ -908,7 +908,7 @@ impl MeshApi { let mut payload = runtime_data::status_payload(runtime_data_collector.build_status_view( runtime_data::StatusViewInput { - version: MESH_LLM_VERSION.to_string(), + version: MESH_LLM_BUILD_VERSION.to_string(), latest_version, node_id, owner: node.owner_summary().await, diff --git a/crates/mesh-llm-host-runtime/src/api/tests.rs b/crates/mesh-llm-host-runtime/src/api/tests.rs index 9745e76108..a6a487b1e2 100644 --- a/crates/mesh-llm-host-runtime/src/api/tests.rs +++ b/crates/mesh-llm-host-runtime/src/api/tests.rs @@ -2620,6 +2620,21 @@ async fn status_includes_external_inference_endpoint_models() { } } +#[tokio::test] +async fn status_reports_local_build_version_and_independent_latest_release() { + let state = build_test_mesh_api().await; + let latest_release = "9.9.9".to_string(); + { + let mut inner = state.inner.lock().await; + inner.latest_version = Some(latest_release.clone()); + } + + let status_body = request_management_json(state, "/api/status").await; + + assert_eq!(status_body["version"], json!(crate::BUILD_VERSION)); + assert_eq!(status_body["latest_version"], json!(latest_release)); +} + #[tokio::test] async fn management_mcp_endpoint_initializes_streamable_http_session() { let state = build_test_mesh_api().await; @@ -4087,7 +4102,7 @@ async fn status_payload_safety_net_adds_self_when_empty() { instances.push(LocalInstance { pid: std::process::id(), api_port: Some(3131), - version: Some(MESH_LLM_VERSION.to_string()), + version: Some(MESH_LLM_BUILD_VERSION.to_string()), started_at_unix: 0, runtime_dir: String::new(), is_self: true, @@ -4098,7 +4113,10 @@ async fn status_payload_safety_net_adds_self_when_empty() { assert!(instances[0].is_self); assert_eq!(instances[0].pid, std::process::id()); assert_eq!(instances[0].api_port, Some(3131)); - assert_eq!(instances[0].version, Some(MESH_LLM_VERSION.to_string())); + assert_eq!( + instances[0].version, + Some(MESH_LLM_BUILD_VERSION.to_string()) + ); } #[test] diff --git a/crates/mesh-llm-host-runtime/src/lib.rs b/crates/mesh-llm-host-runtime/src/lib.rs index e875272677..819f854f95 100644 --- a/crates/mesh-llm-host-runtime/src/lib.rs +++ b/crates/mesh-llm-host-runtime/src/lib.rs @@ -39,7 +39,9 @@ pub use mesh::requirements::{ use anyhow::Result; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const BUILD_VERSION: &str = mesh_llm_build_info::BUILD_VERSION; +pub const RELEASE_VERSION: &str = mesh_llm_build_info::RELEASE_VERSION; +pub const VERSION: &str = RELEASE_VERSION; pub use runtime::{ MeshGuardrailMode, RuntimeOptions, RuntimeSurface, console_session_mode_for_runtime_surface, diff --git a/crates/mesh-llm-host-runtime/src/plugin/mcp.rs b/crates/mesh-llm-host-runtime/src/plugin/mcp.rs index 73f2554ca1..9ed253e889 100644 --- a/crates/mesh-llm-host-runtime/src/plugin/mcp.rs +++ b/crates/mesh-llm-host-runtime/src/plugin/mcp.rs @@ -1346,7 +1346,7 @@ impl ServerHandler for PluginMcpServer { .build(), ) .with_server_info( - Implementation::new("mesh-plugins", env!("CARGO_PKG_VERSION")) + Implementation::new("mesh-plugins", crate::BUILD_VERSION) .with_title("Mesh Plugin MCP") .with_description( "Re-exposes mesh-llm plugins as a single MCP server with the standard MCP surface.", diff --git a/crates/mesh-llm-host-runtime/src/runtime/mod.rs b/crates/mesh-llm-host-runtime/src/runtime/mod.rs index 3dbea27134..8223efb559 100644 --- a/crates/mesh-llm-host-runtime/src/runtime/mod.rs +++ b/crates/mesh-llm-host-runtime/src/runtime/mod.rs @@ -3180,7 +3180,7 @@ fn write_runtime_owner_metadata( let owner_meta = serde_json::json!({ "pid": std::process::id(), "api_port": console_port, - "version": crate::VERSION, + "version": crate::BUILD_VERSION, "started_at_unix": started_at, "mesh_llm_binary": std::env::current_exe() .map(|p| p.to_string_lossy().to_string()) @@ -3569,13 +3569,13 @@ async fn run_runtime_cli( plugin_requested: options.plugin.is_some(), command_is_update: options.command_is_update, llama_flavor: options.llama_flavor, - current_version: crate::VERSION, + current_version: crate::BUILD_VERSION, }) .await?; // Finish the release check before startup continues. if !checked_updates && !options.command_is_update && !options.command_uses_machine_output { - autoupdate::check_for_update(crate::VERSION).await; + autoupdate::check_for_update(crate::BUILD_VERSION).await; } let config = plugin::load_config(options.config.as_deref())?; diff --git a/crates/mesh-llm-host-runtime/src/system/native_runtime.rs b/crates/mesh-llm-host-runtime/src/system/native_runtime.rs index 32f351fbbf..2f20c5cd5a 100644 --- a/crates/mesh-llm-host-runtime/src/system/native_runtime.rs +++ b/crates/mesh-llm-host-runtime/src/system/native_runtime.rs @@ -1,9 +1,12 @@ #[cfg(feature = "dynamic-native-runtime")] mod dynamic { + use crate::system::native_runtime_install::{ + NativeRuntimeInstallOptions, NativeRuntimeInstallOutcome, + }; use anyhow::{Context, Result}; use mesh_llm_native_runtime::{ - HostRuntimeProfile, NativeRuntimeCache, NativeRuntimeReleaseManifest, RuntimeSelection, - select_native_runtime, + HostRuntimeProfile, NativeRuntimeArtifact, NativeRuntimeCache, NativeRuntimeLoadPlan, + NativeRuntimeReleaseManifest, RuntimeSelection, select_native_runtime, }; use std::path::PathBuf; @@ -13,15 +16,165 @@ mod dynamic { pub(crate) libraries: Vec, } + #[derive(Clone, Debug, Eq, PartialEq)] + pub(crate) enum NativeRuntimePlanSource { + CacheHit, + PostInstall, + } + + #[derive(Clone, Debug, Eq, PartialEq)] + pub(crate) struct NativeRuntimeStartupLoadPlan { + pub(crate) cache_mesh_version: String, + pub(crate) native_runtime_id: String, + pub(crate) root: PathBuf, + pub(crate) selected_library_path: PathBuf, + pub(crate) libraries: Vec, + pub(crate) source: NativeRuntimePlanSource, + } + pub(crate) fn try_load_installed_native_runtime() -> Result> { - if skippy_runtime::native_runtime_loaded() { + try_load_installed_native_runtime_with( + skippy_runtime::native_runtime_loaded, + default_native_runtime_cache, + host_runtime_profile, + default_install_options, + default_install_executor, + |libraries| { + unsafe { skippy_runtime::load_native_runtime_libraries(libraries) } + .map_err(anyhow::Error::from) + }, + ) + } + + fn try_load_installed_native_runtime_with< + NativeRuntimeLoadedFn, + CacheFn, + ProfileFn, + InstallOptionsFn, + InstallExecutorFn, + LoadLibrariesFn, + >( + native_runtime_loaded: NativeRuntimeLoadedFn, + cache: CacheFn, + profile: ProfileFn, + install_options: InstallOptionsFn, + install_executor: InstallExecutorFn, + load_libraries: LoadLibrariesFn, + ) -> Result> + where + NativeRuntimeLoadedFn: Fn() -> bool, + CacheFn: Fn() -> Result, + ProfileFn: Fn() -> HostRuntimeProfile, + InstallOptionsFn: Fn() -> NativeRuntimeInstallOptions, + InstallExecutorFn: Fn(NativeRuntimeInstallOptions) -> Result, + LoadLibrariesFn: Fn(&[PathBuf]) -> Result<()>, + { + if native_runtime_loaded() { + return Ok(None); + } + let Some(plan) = resolve_startup_native_runtime_plan_with( + cache, + profile, + install_options, + install_executor, + )? + else { return Ok(None); + }; + load_libraries(&plan.libraries).with_context(|| { + format!( + "load native runtime {} from {}", + plan.native_runtime_id, + plan.root.display() + ) + })?; + Ok(Some(LoadedNativeRuntime { + native_runtime_id: plan.native_runtime_id, + libraries: plan.libraries, + })) + } + + fn resolve_startup_native_runtime_plan_with< + CacheFn, + ProfileFn, + InstallOptionsFn, + InstallExecutorFn, + >( + cache: CacheFn, + profile: ProfileFn, + install_options: InstallOptionsFn, + install_executor: InstallExecutorFn, + ) -> Result> + where + CacheFn: Fn() -> Result, + ProfileFn: Fn() -> HostRuntimeProfile, + InstallOptionsFn: Fn() -> NativeRuntimeInstallOptions, + InstallExecutorFn: Fn(NativeRuntimeInstallOptions) -> Result, + { + let cache = cache()?; + let profile = profile(); + if let Some(plan) = resolve_installed_native_runtime_plan( + &cache, + &profile, + crate::BUILD_VERSION, + crate::RELEASE_VERSION, + &RuntimeSelection::Recommended, + )? { + return Ok(Some(plan)); + } + + let mut options = install_options(); + if options.cache_dir.is_none() { + options.cache_dir = Some(cache.root().to_path_buf()); } - let cache = default_native_runtime_cache()?; + + tracing::info!( + cache_root = %cache.root().display(), + mesh_version = %options.mesh_version, + "No compatible installed MeshLLM native runtime found; attempting one-shot startup install" + ); + + let install_result = install_executor(options.clone()); + match install_result { + Ok(outcome) => { + let load_plan = outcome.runtime.load_plan()?; + Ok(Some(startup_load_plan_from_installed( + outcome.runtime.mesh_version.clone(), + load_plan, + NativeRuntimePlanSource::PostInstall, + )?)) + } + Err(err) => { + tracing::warn!( + error = %err, + cache_root = %cache.root().display(), + mesh_version = %options.mesh_version, + manifest_path = ?options.manifest_path, + manifest_url = ?options.manifest_url, + bundle_dirs = ?options.bundle_dirs, + allow_download = options.allow_download, + "Failed to install a compatible MeshLLM native runtime during startup; continuing without dynamic native runtime" + ); + Ok(None) + } + } + } + + fn resolve_installed_native_runtime_plan( + cache: &NativeRuntimeCache, + profile: &HostRuntimeProfile, + build_version: &str, + release_version: &str, + selection: &RuntimeSelection, + ) -> Result> { let installed = cache.installed()?; - let profile = host_runtime_profile(); + if installed.is_empty() { + return Ok(None); + } + let initial_cache_version = + startup_native_runtime_cache_version(build_version, release_version); let manifest = NativeRuntimeReleaseManifest { - mesh_version: crate::VERSION.to_string(), + mesh_version: initial_cache_version.to_string(), skippy_abi: installed .first() .map(|runtime| runtime.manifest.runtime.skippy_abi.clone()) @@ -31,36 +184,63 @@ mod dynamic { .map(|runtime| runtime.manifest.runtime.clone()) .collect(), }; - let Some(candidate) = select_native_runtime( - &manifest, - &profile, - crate::VERSION, - &RuntimeSelection::Recommended, - ) else { + let Some(candidate) = + select_native_runtime(&manifest, profile, initial_cache_version, selection) + else { return Ok(None); }; + load_plan_from_candidate(cache, &manifest, candidate.artifact) + } + + fn startup_native_runtime_cache_version<'a>( + _build_version: &'a str, + release_version: &'a str, + ) -> &'a str { + release_version + } + + fn load_plan_from_candidate( + cache: &NativeRuntimeCache, + manifest: &NativeRuntimeReleaseManifest, + artifact: NativeRuntimeArtifact, + ) -> Result> { + let cache_mesh_version = artifact + .mesh_version_or(manifest.mesh_version.as_str()) + .to_string(); let installed = cache - .find_installed(crate::VERSION, candidate.artifact.native_runtime_id())? + .find_installed(&cache_mesh_version, artifact.native_runtime_id())? .with_context(|| { format!( "selected native runtime {} disappeared from the cache", - candidate.artifact.native_runtime_id() - ) - })?; - let plan = installed.load_plan()?; - unsafe { - skippy_runtime::load_native_runtime_libraries(&plan.libraries).with_context(|| { - format!( - "load native runtime {} from {}", - plan.native_runtime_id, - plan.root.display() + artifact.native_runtime_id() ) })?; - } - Ok(Some(LoadedNativeRuntime { - native_runtime_id: plan.native_runtime_id, - libraries: plan.libraries, - })) + let load_plan = installed.load_plan()?; + Ok(Some(startup_load_plan_from_installed( + cache_mesh_version, + load_plan, + NativeRuntimePlanSource::CacheHit, + )?)) + } + + fn startup_load_plan_from_installed( + cache_mesh_version: String, + load_plan: NativeRuntimeLoadPlan, + source: NativeRuntimePlanSource, + ) -> Result { + let selected_library_path = load_plan + .libraries + .first() + .cloned() + .context("native runtime load plan did not include a library path")?; + Ok(NativeRuntimeStartupLoadPlan { + cache_mesh_version, + native_runtime_id: load_plan.native_runtime_id, + root: load_plan.root, + selected_library_path, + libraries: load_plan.libraries, + source, + }) } fn default_native_runtime_cache() -> Result { @@ -70,6 +250,308 @@ mod dynamic { fn host_runtime_profile() -> HostRuntimeProfile { crate::system::native_runtime_install::host_runtime_profile() } + + fn default_install_options() -> NativeRuntimeInstallOptions { + NativeRuntimeInstallOptions { + mesh_version: crate::RELEASE_VERSION.to_string(), + selection: RuntimeSelection::Recommended, + ..Default::default() + } + } + + fn default_install_executor( + options: NativeRuntimeInstallOptions, + ) -> Result { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .context("build native runtime startup install executor")? + .block_on(crate::system::native_runtime_install::install_native_runtime(options)) + } + + #[cfg(test)] + mod tests { + use super::*; + use mesh_llm_native_runtime::{ + NativeRuntimeBackend, NativeRuntimeManifest, NativeRuntimePlatform, + }; + use std::{ + fs, + path::Path, + sync::{Arc, Mutex}, + }; + + fn write_runtime(dir: &Path, version: &str, id: &str) { + let library_rel_path = test_library_rel_path(); + fs::create_dir_all(dir.join(library_rel_path.parent().unwrap())).unwrap(); + fs::write(dir.join(&library_rel_path), b"native runtime").unwrap(); + let manifest = NativeRuntimeManifest { + runtime: NativeRuntimeArtifact { + id: id.to_string(), + mesh_version: Some(version.to_string()), + skippy_abi: "0.1.25".to_string(), + platform: NativeRuntimePlatform { + os: std::env::consts::OS.to_string(), + arch: std::env::consts::ARCH.to_string(), + target: None, + }, + backend: NativeRuntimeBackend::cpu(), + rank: 0, + libraries: vec![library_rel_path.to_string_lossy().to_string()], + url: None, + sha256: None, + signature: None, + }, + }; + manifest.write_to_dir(dir).unwrap(); + } + + fn test_library_rel_path() -> PathBuf { + let file = if cfg!(target_os = "windows") { + "meshllm_ffi.dll" + } else if cfg!(target_os = "macos") { + "libmeshllm_ffi.dylib" + } else { + "libmeshllm_ffi.so" + }; + PathBuf::from("lib").join(file) + } + + fn test_install_options() -> NativeRuntimeInstallOptions { + NativeRuntimeInstallOptions { + mesh_version: "0.68.0".to_string(), + allow_download: false, + ..Default::default() + } + } + + #[test] + fn sha_build_uses_release_cache_identity_for_installed_runtime_lookup() { + let temp = tempfile::tempdir().unwrap(); + let cache = NativeRuntimeCache::new(temp.path().join("cache")); + let runtime_id = "meshllm-native-runtime-test-cpu"; + let release_version = "0.68.0"; + let sha_build_version = "0.68.0+gAB131C"; + let runtime_dir = cache.runtime_dir(release_version, runtime_id); + write_runtime(&runtime_dir, release_version, runtime_id); + + let plan = resolve_installed_native_runtime_plan( + &cache, + &HostRuntimeProfile::current_without_gpu_probe(), + sha_build_version, + release_version, + &RuntimeSelection::Recommended, + ) + .unwrap() + .expect("expected cached runtime plan"); + + assert_eq!(plan.cache_mesh_version, release_version); + assert_eq!(plan.native_runtime_id, runtime_id); + assert_eq!(plan.source, NativeRuntimePlanSource::CacheHit); + assert_eq!( + plan.selected_library_path, + runtime_dir.join(test_library_rel_path()) + ); + assert_eq!( + plan.libraries, + vec![runtime_dir.join(test_library_rel_path())] + ); + } + + #[test] + fn selected_artifact_mesh_version_wins_over_release_fallback() { + let temp = tempfile::tempdir().unwrap(); + let cache = NativeRuntimeCache::new(temp.path().join("cache")); + let runtime_id = "meshllm-native-runtime-test-cpu"; + let release_version = "0.68.0"; + let artifact_mesh_version = "0.69.0"; + let runtime_dir = cache.runtime_dir(artifact_mesh_version, runtime_id); + write_runtime(&runtime_dir, artifact_mesh_version, runtime_id); + + let plan = resolve_installed_native_runtime_plan( + &cache, + &HostRuntimeProfile::current_without_gpu_probe(), + "0.68.0+gAB131C.dirty", + release_version, + &RuntimeSelection::Recommended, + ) + .unwrap() + .expect("expected cached runtime plan"); + + assert_eq!(plan.cache_mesh_version, artifact_mesh_version); + assert_eq!(plan.root, runtime_dir); + assert_eq!(plan.source, NativeRuntimePlanSource::CacheHit); + } + + #[test] + fn startup_plan_can_represent_post_install_source_without_loading() { + let temp = tempfile::tempdir().unwrap(); + let runtime_id = "meshllm-native-runtime-test-cpu"; + let release_version = "0.68.0"; + let runtime_dir = temp.path().join(runtime_id); + write_runtime(&runtime_dir, release_version, runtime_id); + let load_plan = NativeRuntimeLoadPlan { + mesh_version: release_version.to_string(), + native_runtime_id: runtime_id.to_string(), + root: runtime_dir.clone(), + libraries: vec![runtime_dir.join(test_library_rel_path())], + }; + + let plan = startup_load_plan_from_installed( + release_version.to_string(), + load_plan, + NativeRuntimePlanSource::PostInstall, + ) + .unwrap(); + + assert_eq!(plan.cache_mesh_version, release_version); + assert_eq!(plan.root, runtime_dir); + assert_eq!(plan.source, NativeRuntimePlanSource::PostInstall); + } + + #[test] + fn cache_hit_skips_install_and_loads_cached_runtime_once() { + let temp = tempfile::tempdir().unwrap(); + let cache = NativeRuntimeCache::new(temp.path().join("cache")); + let runtime_id = "meshllm-native-runtime-test-cpu"; + let release_version = "0.68.0"; + let runtime_dir = cache.runtime_dir(release_version, runtime_id); + write_runtime(&runtime_dir, release_version, runtime_id); + + let install_calls = Arc::new(Mutex::new(0_usize)); + let load_calls = Arc::new(Mutex::new(Vec::>::new())); + + let runtime = try_load_installed_native_runtime_with( + || false, + || Ok(cache.clone()), + HostRuntimeProfile::current_without_gpu_probe, + test_install_options, + { + let install_calls = Arc::clone(&install_calls); + move |_| { + *install_calls.lock().unwrap() += 1; + anyhow::bail!("install should not run on cache hit") + } + }, + { + let load_calls = Arc::clone(&load_calls); + move |libraries| { + load_calls.lock().unwrap().push(libraries.to_vec()); + Ok(()) + } + }, + ) + .unwrap() + .expect("expected cached runtime to load"); + + assert_eq!(*install_calls.lock().unwrap(), 0); + assert_eq!(runtime.native_runtime_id, runtime_id); + assert_eq!( + runtime.libraries, + vec![runtime_dir.join(test_library_rel_path())] + ); + assert_eq!(load_calls.lock().unwrap().as_slice(), &[runtime.libraries]); + } + + #[test] + fn cache_miss_installs_once_and_loads_post_install_runtime() { + let temp = tempfile::tempdir().unwrap(); + let cache = NativeRuntimeCache::new(temp.path().join("cache")); + let bundle_dir = temp.path().join("bundle"); + let runtime_id = "meshllm-native-runtime-test-cpu"; + let manifest_mesh_version = "0.69.0"; + write_runtime(&bundle_dir, manifest_mesh_version, runtime_id); + + let install_calls = Arc::new(Mutex::new(Vec::::new())); + let load_calls = Arc::new(Mutex::new(Vec::>::new())); + + let runtime = try_load_installed_native_runtime_with( + || false, + || Ok(cache.clone()), + HostRuntimeProfile::current_without_gpu_probe, + test_install_options, + { + let install_calls = Arc::clone(&install_calls); + let bundle_dir = bundle_dir.clone(); + let cache = cache.clone(); + move |mut options| { + install_calls.lock().unwrap().push(options.clone()); + let source = options.bundle_dirs.pop().unwrap_or(bundle_dir.clone()); + let runtime = cache.install_from_dir(&source)?; + Ok(NativeRuntimeInstallOutcome { + status: crate::system::native_runtime_install::NativeRuntimeInstallStatus::Installed, + runtime, + resolution: mesh_llm_native_runtime::NativeRuntimeResolution { + source: mesh_llm_native_runtime::NativeRuntimeSource::Bundle { path: source }, + selected: NativeRuntimeManifest::read_from_dir(&bundle_dir)?.runtime, + evaluated: Vec::new(), + }, + }) + } + }, + { + let load_calls = Arc::clone(&load_calls); + move |libraries| { + load_calls.lock().unwrap().push(libraries.to_vec()); + Ok(()) + } + }, + ) + .unwrap() + .expect("expected installed runtime to load"); + + let recorded_options = install_calls.lock().unwrap(); + assert_eq!(recorded_options.len(), 1); + assert_eq!(recorded_options[0].mesh_version, "0.68.0"); + assert_eq!(recorded_options[0].cache_dir.as_deref(), Some(cache.root())); + assert_eq!(runtime.native_runtime_id, runtime_id); + assert_eq!( + runtime.libraries, + vec![ + cache + .runtime_dir(manifest_mesh_version, runtime_id) + .join(test_library_rel_path()) + ] + ); + assert_eq!(load_calls.lock().unwrap().as_slice(), &[runtime.libraries]); + } + + #[test] + fn cache_miss_install_failure_returns_none_without_retry() { + let temp = tempfile::tempdir().unwrap(); + let cache = NativeRuntimeCache::new(temp.path().join("cache")); + let install_calls = Arc::new(Mutex::new(Vec::::new())); + let load_calls = Arc::new(Mutex::new(0_usize)); + + let runtime = try_load_installed_native_runtime_with( + || false, + || Ok(cache.clone()), + HostRuntimeProfile::current_without_gpu_probe, + test_install_options, + { + let install_calls = Arc::clone(&install_calls); + move |options| { + install_calls.lock().unwrap().push(options); + anyhow::bail!( + "no compatible native runtime found for Skippy ABI 0.1.25 on test/test" + ) + } + }, + { + let load_calls = Arc::clone(&load_calls); + move |_| { + *load_calls.lock().unwrap() += 1; + Ok(()) + } + }, + ) + .unwrap(); + + assert!(runtime.is_none()); + assert_eq!(install_calls.lock().unwrap().len(), 1); + assert_eq!(*load_calls.lock().unwrap(), 0); + } + } } #[cfg(feature = "dynamic-native-runtime")] diff --git a/crates/mesh-llm-runtime-install/Cargo.toml b/crates/mesh-llm-runtime-install/Cargo.toml index 2e18ef100e..fd29f0f1af 100644 --- a/crates/mesh-llm-runtime-install/Cargo.toml +++ b/crates/mesh-llm-runtime-install/Cargo.toml @@ -17,6 +17,7 @@ dirs = "6.0.0" flate2 = "1" futures-util = "0.3" hex = "0.4" +mesh-llm-build-info.workspace = true mesh-llm-hardware-profile = { path = "../mesh-llm-hardware-profile", version = "0.68.0" } mesh-llm-native-runtime = { path = "../mesh-llm-native-runtime", version = "0.68.0" } reqwest = { version = "0.12", features = ["stream", "json"] } @@ -26,4 +27,4 @@ sha2.workspace = true skippy-ffi = { path = "../skippy-ffi", version = "0.68.0", default-features = false } tar = "0.4" tempfile = "3" -tokio = { version = "1", features = ["fs", "io-util"] } +tokio = { version = "1", features = ["fs", "io-util", "rt"] } diff --git a/crates/mesh-llm-runtime-install/src/lib.rs b/crates/mesh-llm-runtime-install/src/lib.rs index 08615989a3..1ff0344ea8 100644 --- a/crates/mesh-llm-runtime-install/src/lib.rs +++ b/crates/mesh-llm-runtime-install/src/lib.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::io::AsyncWriteExt; -pub const CURRENT_MESH_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CURRENT_MESH_VERSION: &str = mesh_llm_build_info::RELEASE_VERSION; pub const NATIVE_RUNTIME_CACHE_DIR_ENV: &str = "MESH_LLM_NATIVE_RUNTIME_CACHE_DIR"; pub const NATIVE_RUNTIME_MANIFEST_URL_ENV: &str = "MESH_LLM_NATIVE_RUNTIME_MANIFEST_URL"; @@ -113,6 +113,26 @@ pub fn default_release_manifest_url(mesh_version: &str) -> String { ) } +pub fn default_manifest_url(build_version: &str, release_version: &str) -> String { + if mesh_llm_build_info::is_sha_build(build_version) { + "https://github.com/Mesh-LLM/mesh-llm/releases/latest/download/native-runtimes.json" + .to_string() + } else { + default_release_manifest_url(release_version) + } +} + +fn request_default_manifest_url(mesh_version: &str) -> String { + if mesh_version == mesh_llm_build_info::RELEASE_VERSION { + default_manifest_url( + mesh_llm_build_info::BUILD_VERSION, + mesh_llm_build_info::RELEASE_VERSION, + ) + } else { + default_release_manifest_url(mesh_version) + } +} + pub fn current_skippy_abi_version() -> String { format!( "{}.{}.{}", @@ -156,7 +176,7 @@ pub async fn load_release_manifest( mesh_version = manifest.mesh_version.clone(); skippy_abi = manifest.skippy_abi.clone(); artifacts.extend(manifest.artifacts); - } else if let Some(url) = manifest_url(&mesh_version, &options) { + } else if let Some(url) = manifest_url(&options) { let manifest = download_release_manifest(&url).await?; mesh_version = manifest.mesh_version.clone(); skippy_abi = manifest.skippy_abi.clone(); @@ -398,7 +418,7 @@ fn emit_download_progress( }); } -fn manifest_url(mesh_version: &str, options: &NativeRuntimeManifestOptions) -> Option { +fn manifest_url(options: &NativeRuntimeManifestOptions) -> Option { options .manifest_url .clone() @@ -409,7 +429,7 @@ fn manifest_url(mesh_version: &str, options: &NativeRuntimeManifestOptions) -> O }) .or_else(|| { (options.allow_default_manifest_url && options.bundle_dirs.is_empty()) - .then(|| default_release_manifest_url(mesh_version)) + .then(|| request_default_manifest_url(&options.mesh_version)) }) } @@ -490,6 +510,9 @@ fn collect_runtime_manifest_dirs(dir: &Path, matches: &mut Vec) -> Resu mod tests { use super::*; use mesh_llm_native_runtime::{NativeRuntimeBackend, NativeRuntimePlatform}; + use std::sync::Mutex; + + static MANIFEST_ENV_LOCK: Mutex<()> = Mutex::new(()); fn artifact_with_sha(signature: Option<&str>) -> NativeRuntimeArtifact { NativeRuntimeArtifact { @@ -546,11 +569,155 @@ mod tests { #[test] fn default_manifest_url_is_skipped_for_bundle_only_resolution() { + let _guard = MANIFEST_ENV_LOCK.lock().unwrap(); + unsafe { + std::env::remove_var(NATIVE_RUNTIME_MANIFEST_URL_ENV); + } + let options = NativeRuntimeManifestOptions { bundle_dirs: vec![PathBuf::from("runtime-bundle")], ..Default::default() }; - assert!(manifest_url(CURRENT_MESH_VERSION, &options).is_none()); + assert!(manifest_url(&options).is_none()); + } + + #[test] + fn explicit_manifest_url_wins_over_env_and_default() { + let _guard = MANIFEST_ENV_LOCK.lock().unwrap(); + unsafe { + std::env::set_var( + NATIVE_RUNTIME_MANIFEST_URL_ENV, + "https://example.invalid/from-env.json", + ); + } + + let options = NativeRuntimeManifestOptions { + manifest_url: Some("https://example.invalid/from-arg.json".to_string()), + ..Default::default() + }; + + assert_eq!( + manifest_url(&options).as_deref(), + Some("https://example.invalid/from-arg.json") + ); + + unsafe { + std::env::remove_var(NATIVE_RUNTIME_MANIFEST_URL_ENV); + } + } + + #[test] + fn env_manifest_url_wins_over_default() { + let _guard = MANIFEST_ENV_LOCK.lock().unwrap(); + unsafe { + std::env::set_var( + NATIVE_RUNTIME_MANIFEST_URL_ENV, + "https://example.invalid/from-env.json", + ); + } + + let url = manifest_url(&NativeRuntimeManifestOptions::default()); + + assert_eq!( + url.as_deref(), + Some("https://example.invalid/from-env.json") + ); + + unsafe { + std::env::remove_var(NATIVE_RUNTIME_MANIFEST_URL_ENV); + } + } + + #[test] + fn default_manifest_url_uses_release_download_for_release_builds() { + assert_eq!( + default_manifest_url("0.68.0", "0.68.0"), + "https://github.com/Mesh-LLM/mesh-llm/releases/download/v0.68.0/native-runtimes.json" + ); + } + + #[test] + fn default_manifest_url_uses_latest_download_for_sha_builds() { + assert_eq!( + default_manifest_url("0.68.0+gAB131C", "0.68.0"), + "https://github.com/Mesh-LLM/mesh-llm/releases/latest/download/native-runtimes.json" + ); + assert_eq!( + default_manifest_url("0.68.0+gAB131C.dirty", "0.68.0"), + "https://github.com/Mesh-LLM/mesh-llm/releases/latest/download/native-runtimes.json" + ); + } + + #[test] + fn non_default_mesh_version_request_uses_versioned_release_url() { + let _guard = MANIFEST_ENV_LOCK.lock().unwrap(); + unsafe { + std::env::remove_var(NATIVE_RUNTIME_MANIFEST_URL_ENV); + } + + let options = NativeRuntimeManifestOptions { + mesh_version: "0.67.0".to_string(), + allow_default_manifest_url: true, + ..Default::default() + }; + + assert_eq!( + manifest_url(&options).as_deref(), + Some( + "https://github.com/Mesh-LLM/mesh-llm/releases/download/v0.67.0/native-runtimes.json" + ) + ); + } + + #[test] + fn current_mesh_version_uses_release_version() { + assert_eq!(CURRENT_MESH_VERSION, mesh_llm_build_info::RELEASE_VERSION); + } + + #[test] + fn load_release_manifest_prefers_explicit_path_over_env_and_default() { + let _guard = MANIFEST_ENV_LOCK.lock().unwrap(); + unsafe { + std::env::set_var( + NATIVE_RUNTIME_MANIFEST_URL_ENV, + "https://example.invalid/should-not-be-fetched.json", + ); + } + + let temp = tempfile::tempdir().unwrap(); + let path = temp.path().join("native-runtimes.json"); + std::fs::write( + &path, + format!( + r#"{{ + "mesh_version": "0.68.0", + "skippy_abi": "{}", + "artifacts": [] +}}"#, + current_skippy_abi_version() + ), + ) + .unwrap(); + + let manifest = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(load_release_manifest(NativeRuntimeManifestOptions { + mesh_version: "0.0.0+gLOCAL".to_string(), + manifest_path: Some(path), + manifest_url: Some("https://example.invalid/from-arg.json".to_string()), + bundle_dirs: Vec::new(), + allow_default_manifest_url: true, + })) + .unwrap(); + + assert_eq!(manifest.mesh_version, "0.68.0"); + assert!(manifest.artifacts.is_empty()); + + unsafe { + std::env::remove_var(NATIVE_RUNTIME_MANIFEST_URL_ENV); + } } } diff --git a/crates/mesh-llm-system/Cargo.toml b/crates/mesh-llm-system/Cargo.toml index 4d015ba6fa..0d0ee41b4e 100644 --- a/crates/mesh-llm-system/Cargo.toml +++ b/crates/mesh-llm-system/Cargo.toml @@ -14,6 +14,7 @@ clap = { version = "4", features = ["derive"] } dirs = "6.0.0" hex = "0.4.3" libc = "0.2.183" +mesh-llm-build-info.workspace = true mesh-llm-gpu-bench = { path = "../mesh-llm-gpu-bench", version = "0.68.0" } reqwest = { version = "0.12", features = ["stream", "json"] } semver = "1" diff --git a/crates/mesh-llm-system/src/autoupdate.rs b/crates/mesh-llm-system/src/autoupdate.rs index 51d3f5cf0d..f0f8b0e6b8 100644 --- a/crates/mesh-llm-system/src/autoupdate.rs +++ b/crates/mesh-llm-system/src/autoupdate.rs @@ -256,10 +256,23 @@ async fn fetch_release_info(url: &str) -> Option { } pub fn version_newer(a: &str, b: &str) -> bool { - match (semver::Version::parse(a), semver::Version::parse(b)) { - (Ok(a), Ok(b)) => a > b, - _ => false, + let (Ok(a_parsed), Ok(b_parsed)) = (semver::Version::parse(a), semver::Version::parse(b)) + else { + return false; + }; + + let a_is_sha = mesh_llm_build_info::is_sha_build(a); + let b_is_sha = mesh_llm_build_info::is_sha_build(b); + + if !a_is_sha && b_is_sha { + return false; } + + if a_is_sha && !b_is_sha { + return true; + } + + a_parsed > b_parsed } fn should_attempt_auto_update(options: AutoUpdateOptions) -> bool { @@ -1512,6 +1525,16 @@ mod tests { assert!(version_newer("0.33.0", "0.33.0-rc.1")); assert!(!version_newer("0.33.0-rc.1", "0.33.0")); assert!(version_newer("0.33.0-rc.2", "0.33.0-rc.1")); + assert!(!version_newer("0.99.0", "0.68.0+gAB131C")); + assert!(version_newer("0.68.0+gAB131C", "0.99.0")); + assert!(!version_newer("0.99.0", "0.68.0+gAB131C.dirty")); + assert!(version_newer("0.68.0+gAB131C.dirty", "0.99.0")); + assert!(version_newer("0.69.0", "0.68.0")); + assert!(!version_newer("not-a-version", "0.68.0")); + assert!(!version_newer("0.68.0", "not-a-version")); + assert!(!version_newer("not-a-version+gAB131C", "0.99.0")); + assert!(!version_newer("not-a-version+gAB131C.dirty", "0.99.0")); + assert!(!version_newer("0.99.0", "not-a-version+gAB131C")); } #[test] diff --git a/scripts/affected-crates.sh b/scripts/affected-crates.sh index 83728b6b51..4ff436ac3a 100755 --- a/scripts/affected-crates.sh +++ b/scripts/affected-crates.sh @@ -9,6 +9,7 @@ set -euo pipefail # Hardcoded workspace members (fallback for fail-open) WORKSPACE_MEMBERS=( "mesh-llm" + "mesh-llm-build-info" "mesh-llm-cli" "mesh-llm-commands" "mesh-llm-config" diff --git a/scripts/build-linux.sh b/scripts/build-linux.sh index 5f77b7fd33..62c3a6006c 100755 --- a/scripts/build-linux.sh +++ b/scripts/build-linux.sh @@ -28,6 +28,50 @@ append_rustflag() { esac } +stamp_build_version() { + local release_version="" + local pkgid="" + local sha="" + local dirty_suffix="" + local status_output="" + + if [[ -n "${MESH_LLM_BUILD_VERSION:-}" ]]; then + echo "Using preset MESH_LLM_BUILD_VERSION: $MESH_LLM_BUILD_VERSION" + return 0 + fi + + if ! pkgid="$(cd "$REPO_ROOT" && cargo pkgid -p mesh-llm 2>/dev/null)"; then + echo "Warning: unable to derive build version; cargo pkgid unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + release_version="${pkgid##*#}" + if [[ -z "$release_version" || "$release_version" == "$pkgid" ]]; then + echo "Warning: unable to derive build version; cargo pkgid output was unexpected." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + + if ! sha="$(git -C "$REPO_ROOT" rev-parse --short=6 HEAD 2>/dev/null)"; then + echo "Warning: unable to derive build version; git SHA unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + sha="$(printf '%s' "$sha" | tr '[:lower:]' '[:upper:]')" + + if ! status_output="$(git -C "$REPO_ROOT" status --porcelain --untracked-files=all 2>/dev/null)"; then + echo "Warning: unable to derive build version; git status unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + if [[ -n "$status_output" ]]; then + dirty_suffix=".dirty" + fi + + export MESH_LLM_BUILD_VERSION="${release_version}+g${sha}${dirty_suffix}" + echo "Derived MESH_LLM_BUILD_VERSION: $MESH_LLM_BUILD_VERSION" +} + configure_lld_linker() { if ! command -v ld.lld >/dev/null 2>&1; then cat >&2 <<'EOF' @@ -246,6 +290,7 @@ if [[ "${MESH_LLM_BUILD_PROFILE:-debug}" == "dev" || "${MESH_LLM_BUILD_PROFILE:- cuda) cargo_features=(--features gpu-bench-cuda) ;; rocm) cargo_features=(--features gpu-bench-hip) ;; esac + stamp_build_version (cd "$REPO_ROOT" && cargo build -p mesh-llm --bin mesh-llm "${cargo_features[@]}") echo "Mesh binary: target/debug/mesh-llm" elif [[ "${MESH_LLM_BUILD_PROFILE:-debug}" == "release" ]]; then @@ -255,6 +300,7 @@ elif [[ "${MESH_LLM_BUILD_PROFILE:-debug}" == "release" ]]; then cuda) cargo_features=(--features gpu-bench-cuda) ;; rocm) cargo_features=(--features gpu-bench-hip) ;; esac + stamp_build_version (cd "$REPO_ROOT" && cargo build --release -p mesh-llm "${cargo_features[@]}") echo "Mesh binary: target/release/mesh-llm" else diff --git a/scripts/build-mac.sh b/scripts/build-mac.sh index d3fc3dcebc..541ace23d9 100755 --- a/scripts/build-mac.sh +++ b/scripts/build-mac.sh @@ -22,6 +22,50 @@ append_rustflag() { esac } +stamp_build_version() { + local release_version="" + local pkgid="" + local sha="" + local dirty_suffix="" + local status_output="" + + if [[ -n "${MESH_LLM_BUILD_VERSION:-}" ]]; then + echo "Using preset MESH_LLM_BUILD_VERSION: $MESH_LLM_BUILD_VERSION" + return 0 + fi + + if ! pkgid="$(cd "$REPO_ROOT" && cargo pkgid -p mesh-llm 2>/dev/null)"; then + echo "Warning: unable to derive build version; cargo pkgid unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + release_version="${pkgid##*#}" + if [[ -z "$release_version" || "$release_version" == "$pkgid" ]]; then + echo "Warning: unable to derive build version; cargo pkgid output was unexpected." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + + if ! sha="$(git -C "$REPO_ROOT" rev-parse --short=6 HEAD 2>/dev/null)"; then + echo "Warning: unable to derive build version; git SHA unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + sha="$(printf '%s' "$sha" | tr '[:lower:]' '[:upper:]')" + + if ! status_output="$(git -C "$REPO_ROOT" status --porcelain --untracked-files=all 2>/dev/null)"; then + echo "Warning: unable to derive build version; git status unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + if [[ -n "$status_output" ]]; then + dirty_suffix=".dirty" + fi + + export MESH_LLM_BUILD_VERSION="${release_version}+g${sha}${dirty_suffix}" + echo "Derived MESH_LLM_BUILD_VERSION: $MESH_LLM_BUILD_VERSION" +} + configure_lld_linker() { local lld="" local lld_prefix="" @@ -93,6 +137,7 @@ if [[ -d "$MESH_DIR" ]]; then case "$build_profile" in dev|debug) echo "Building mesh-llm (profile: dev, bin only)..." + stamp_build_version if [[ -n "$rustc_wrapper" ]]; then (cd "$REPO_ROOT" && RUSTC_WRAPPER="$rustc_wrapper" cargo build -p mesh-llm --bin mesh-llm) else @@ -102,6 +147,7 @@ if [[ -d "$MESH_DIR" ]]; then ;; release) echo "Building mesh-llm (profile: release)..." + stamp_build_version if [[ -n "$rustc_wrapper" ]]; then (cd "$REPO_ROOT" && RUSTC_WRAPPER="$rustc_wrapper" cargo build --release -p mesh-llm) else diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 24de97e9f1..59caa78274 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -18,6 +18,50 @@ append_rustflag() { esac } +stamp_build_version() { + local release_version="" + local pkgid="" + local sha="" + local dirty_suffix="" + local status_output="" + + if [[ -n "${MESH_LLM_BUILD_VERSION:-}" ]]; then + echo "Using preset MESH_LLM_BUILD_VERSION: $MESH_LLM_BUILD_VERSION" + return 0 + fi + + if ! pkgid="$(cd "$REPO_ROOT" && cargo pkgid -p mesh-llm 2>/dev/null)"; then + echo "Warning: unable to derive build version; cargo pkgid unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + release_version="${pkgid##*#}" + if [[ -z "$release_version" || "$release_version" == "$pkgid" ]]; then + echo "Warning: unable to derive build version; cargo pkgid output was unexpected." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + + if ! sha="$(git -C "$REPO_ROOT" rev-parse --short=6 HEAD 2>/dev/null)"; then + echo "Warning: unable to derive build version; git SHA unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + sha="$(printf '%s' "$sha" | tr '[:lower:]' '[:upper:]')" + + if ! status_output="$(git -C "$REPO_ROOT" status --porcelain --untracked-files=all 2>/dev/null)"; then + echo "Warning: unable to derive build version; git status unavailable." >&2 + unset MESH_LLM_BUILD_VERSION || true + return 0 + fi + if [[ -n "$status_output" ]]; then + dirty_suffix=".dirty" + fi + + export MESH_LLM_BUILD_VERSION="${release_version}+g${sha}${dirty_suffix}" + echo "Derived MESH_LLM_BUILD_VERSION: $MESH_LLM_BUILD_VERSION" +} + configure_lld_linker() { case "$(uname -s)" in Linux) @@ -133,4 +177,5 @@ cargo_features=() if [[ "$DYNAMIC_NATIVE_RUNTIME" == "1" ]]; then cargo_features=(--features dynamic-native-runtime) fi +stamp_build_version (cd "$REPO_ROOT" && cargo build --release --locked -p mesh-llm "${cargo_features[@]}") diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index 1a2f6b7606..ab30f20c64 100755 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -192,6 +192,66 @@ function Configure-CompilerCache { ) } +function Set-BuildVersionStamp { + if ($env:MESH_LLM_BUILD_VERSION) { + Write-Host "Using preset MESH_LLM_BUILD_VERSION: $($env:MESH_LLM_BUILD_VERSION)" + return + } + + $pkgid = $null + try { + Push-Location $repoRoot + $pkgid = (& cargo pkgid -p mesh-llm 2>$null).Trim() + if ($LASTEXITCODE -ne 0 -or -not $pkgid) { + Write-Warning "Unable to derive build version; cargo pkgid unavailable." + Remove-Item Env:MESH_LLM_BUILD_VERSION -ErrorAction SilentlyContinue + return + } + } finally { + Pop-Location + } + + $releaseVersion = $pkgid.Substring($pkgid.LastIndexOf('#') + 1) + if (-not $releaseVersion -or $releaseVersion -eq $pkgid) { + Write-Warning "Unable to derive build version; cargo pkgid output was unexpected." + Remove-Item Env:MESH_LLM_BUILD_VERSION -ErrorAction SilentlyContinue + return + } + + $sha = $null + try { + $sha = (& git -C $repoRoot rev-parse --short=6 HEAD 2>$null).Trim() + if ($LASTEXITCODE -ne 0 -or -not $sha) { + Write-Warning "Unable to derive build version; git SHA unavailable." + Remove-Item Env:MESH_LLM_BUILD_VERSION -ErrorAction SilentlyContinue + return + } + } catch { + Write-Warning "Unable to derive build version; git SHA unavailable." + Remove-Item Env:MESH_LLM_BUILD_VERSION -ErrorAction SilentlyContinue + return + } + $sha = $sha.ToUpperInvariant() + + $statusOutput = $null + try { + $statusOutput = (& git -C $repoRoot status --porcelain --untracked-files=all 2>$null) + if ($LASTEXITCODE -ne 0) { + Write-Warning "Unable to derive build version; git status unavailable." + Remove-Item Env:MESH_LLM_BUILD_VERSION -ErrorAction SilentlyContinue + return + } + } catch { + Write-Warning "Unable to derive build version; git status unavailable." + Remove-Item Env:MESH_LLM_BUILD_VERSION -ErrorAction SilentlyContinue + return + } + + $dirtySuffix = if ($statusOutput) { ".dirty" } else { "" } + $env:MESH_LLM_BUILD_VERSION = "$releaseVersion+g$sha$dirtySuffix" + Write-Host "Derived MESH_LLM_BUILD_VERSION: $($env:MESH_LLM_BUILD_VERSION)" +} + function Test-Sccache { return $compilerCacheBin -and ((Split-Path -Leaf $compilerCacheBin).ToLowerInvariant() -like "sccache*") } @@ -1078,6 +1138,7 @@ Invoke-InRepo { "cuda" { $cargoFeatureArgs = @("--features", "gpu-bench-cuda") } "rocm" { $cargoFeatureArgs = @("--features", "gpu-bench-hip") } } + Set-BuildVersionStamp switch ($buildProfile) { "dev" { Invoke-NativeCommand "cargo" (@("build", "-p", "mesh-llm", "--bin", "mesh-llm") + $cargoFeatureArgs) diff --git a/scripts/plan-clippy-batches.sh b/scripts/plan-clippy-batches.sh index bd1b0bfba3..724545dcc1 100644 --- a/scripts/plan-clippy-batches.sh +++ b/scripts/plan-clippy-batches.sh @@ -12,6 +12,7 @@ set -euo pipefail WORKSPACE_MEMBERS=( "mesh-llm" + "mesh-llm-build-info" "mesh-llm-cli" "mesh-llm-commands" "mesh-llm-config" @@ -129,6 +130,7 @@ crates = json.loads(sys.argv[2]) # Unknown crates intentionally default to 1 so new crates still get scheduled. weights = { "mesh-llm": 10, + "mesh-llm-build-info": 1, "mesh-llm-cli": 1, "mesh-llm-commands": 1, "mesh-llm-events": 1, diff --git a/scripts/publish-crates.sh b/scripts/publish-crates.sh index 84e8ce84fd..8fe4a47217 100755 --- a/scripts/publish-crates.sh +++ b/scripts/publish-crates.sh @@ -340,6 +340,7 @@ unpublished_registry_deps() { ;; mesh-llm-runtime-install) printf '%s\n' \ + mesh-llm-build-info \ mesh-llm-hardware-profile \ mesh-llm-native-runtime \ skippy-ffi @@ -362,6 +363,7 @@ unpublished_registry_deps() { ;; mesh-llm-cli) printf '%s\n' \ + mesh-llm-build-info \ mesh-llm-events ;; mesh-llm-tui) @@ -374,6 +376,7 @@ unpublished_registry_deps() { ;; mesh-llm-system) printf '%s\n' \ + mesh-llm-build-info \ mesh-llm-gpu-bench \ skippy-runtime ;; @@ -419,6 +422,7 @@ unpublished_registry_deps() { mesh-llm-host-runtime) printf '%s\n' \ mesh-llm-api-server \ + mesh-llm-build-info \ mesh-llm-client \ mesh-llm-config \ mesh-llm-events \ @@ -508,6 +512,7 @@ publish_crates=( mesh-llm-client mesh-llm-api-client mesh-llm-events + mesh-llm-build-info mesh-llm-config mesh-llm-ui mesh-llm-console-server diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs index 66ca24d6e5..6617e7eced 100644 --- a/tools/xtask/src/main.rs +++ b/tools/xtask/src/main.rs @@ -745,10 +745,14 @@ fn resolve_runtime_version(repo_root: &Path) -> DynResult { fn extract_runtime_version(repo_root: &Path, contents: &str) -> DynResult { const LITERAL_PREFIX: &str = "pub const VERSION: &str = \""; const CARGO_PKG_VERSION: &str = "pub const VERSION: &str = env!(\"CARGO_PKG_VERSION\");"; + const RELEASE_VERSION_ALIAS: &str = "pub const VERSION: &str = RELEASE_VERSION;"; for line in contents.lines().map(str::trim) { if line == CARGO_PKG_VERSION { return host_runtime_package_version(repo_root); } + if line == RELEASE_VERSION_ALIAS { + return host_runtime_package_version(repo_root); + } if let Some(rest) = line.strip_prefix(LITERAL_PREFIX) { return Ok(rest .strip_suffix("\";") From 0168ac9096ffac8c724803e8bb4073cfde1f2dbd Mon Sep 17 00:00:00 2001 From: Nick DiZazzo Date: Thu, 11 Jun 2026 23:52:59 -0400 Subject: [PATCH 2/3] fix docs lint --- website/src/docs/pages/CLI.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/src/docs/pages/CLI.md b/website/src/docs/pages/CLI.md index a6e5151db5..2a9de24c92 100644 --- a/website/src/docs/pages/CLI.md +++ b/website/src/docs/pages/CLI.md @@ -14,6 +14,15 @@ mesh-llm models --help mesh-llm models --help ``` +## Check the running version + +```bash +mesh-llm --version +``` + +Release builds report the released package version, such as `mesh-llm 0.68.0`. +Local source builds may include build metadata, such as `mesh-llm 0.68.0+gABCDEF.dirty`, so you can tell exactly which commit produced the binary. Compatibility checks, native-runtime cache paths, and release identity still use the plain release version. + ## Start here (common tasks) If you want to: From b65f1adcbc79c72087e6c143e7e8fabb2f3a35ca Mon Sep 17 00:00:00 2001 From: Nick DiZazzo Date: Fri, 12 Jun 2026 00:32:42 -0400 Subject: [PATCH 3/3] address comment --- .../src/system/native_runtime.rs | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/crates/mesh-llm-host-runtime/src/system/native_runtime.rs b/crates/mesh-llm-host-runtime/src/system/native_runtime.rs index 2f20c5cd5a..52a3793c13 100644 --- a/crates/mesh-llm-host-runtime/src/system/native_runtime.rs +++ b/crates/mesh-llm-host-runtime/src/system/native_runtime.rs @@ -207,14 +207,11 @@ mod dynamic { let cache_mesh_version = artifact .mesh_version_or(manifest.mesh_version.as_str()) .to_string(); - let installed = cache - .find_installed(&cache_mesh_version, artifact.native_runtime_id())? - .with_context(|| { - format!( - "selected native runtime {} disappeared from the cache", - artifact.native_runtime_id() - ) - })?; + let Some(installed) = + cache.find_installed(&cache_mesh_version, artifact.native_runtime_id())? + else { + return Ok(None); + }; let load_plan = installed.load_plan()?; Ok(Some(startup_load_plan_from_installed( cache_mesh_version, @@ -409,6 +406,39 @@ mod dynamic { assert_eq!(plan.source, NativeRuntimePlanSource::PostInstall); } + #[test] + fn disappeared_cache_entry_is_treated_as_cache_miss() { + let temp = tempfile::tempdir().unwrap(); + let cache = NativeRuntimeCache::new(temp.path().join("cache")); + let runtime_id = "meshllm-native-runtime-test-cpu"; + let release_version = "0.68.0"; + let manifest = NativeRuntimeReleaseManifest { + mesh_version: release_version.to_string(), + skippy_abi: "0.1.25".to_string(), + artifacts: Vec::new(), + }; + let artifact = NativeRuntimeArtifact { + id: runtime_id.to_string(), + mesh_version: Some(release_version.to_string()), + skippy_abi: "0.1.25".to_string(), + platform: NativeRuntimePlatform { + os: std::env::consts::OS.to_string(), + arch: std::env::consts::ARCH.to_string(), + target: None, + }, + backend: NativeRuntimeBackend::cpu(), + rank: 0, + libraries: vec![test_library_rel_path().to_string_lossy().to_string()], + url: None, + sha256: None, + signature: None, + }; + + let plan = load_plan_from_candidate(&cache, &manifest, artifact).unwrap(); + + assert!(plan.is_none()); + } + #[test] fn cache_hit_skips_install_and_loads_cached_runtime_once() { let temp = tempfile::tempdir().unwrap();