From ff06b9449c8f5928df88486e802b782243436c11 Mon Sep 17 00:00:00 2001 From: James Dumay Date: Thu, 4 Jun 2026 08:45:49 +1000 Subject: [PATCH 1/7] add runtime installer script --- install-runtime.sh | 253 +++++++++++++++++++++++ scripts/tests/test_install_runtime_sh.py | 179 ++++++++++++++++ scripts/tests/test_install_sh.py | 162 +-------------- 3 files changed, 442 insertions(+), 152 deletions(-) create mode 100755 install-runtime.sh create mode 100644 scripts/tests/test_install_runtime_sh.py diff --git a/install-runtime.sh b/install-runtime.sh new file mode 100755 index 0000000000..9bde98014b --- /dev/null +++ b/install-runtime.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Runtime releases currently live on the prerelease channel. Keep install.sh as +# the stable bundled-runtime installer and make this script runtime-first. +MESH_LLM_INSTALL_PRERELEASE="${MESH_LLM_INSTALL_PRERELEASE:-1}" + +source_installer_helpers() { + if [[ -n "${MESH_LLM_RUNTIME_INSTALL_HELPERS:-}" ]]; then + # shellcheck source=/dev/null + . "$MESH_LLM_RUNTIME_INSTALL_HELPERS" + return 0 + fi + + local source_path="${BASH_SOURCE[0]-}" + local script_dir + if [[ -n "$source_path" && "$source_path" == */* ]]; then + script_dir="$(cd "$(dirname "$source_path")" && pwd)" + if [[ -f "$script_dir/install.sh" ]]; then + # shellcheck source=install.sh + . "$script_dir/install.sh" + return 0 + fi + fi + + local repo="${MESH_LLM_INSTALL_REPO:-Mesh-LLM/mesh-llm}" + local ref="${MESH_LLM_INSTALL_REF:-main}" + local helper_file + + if ! command -v curl >/dev/null 2>&1; then + echo "error: required command not found: curl" >&2 + exit 1 + fi + if ! command -v mktemp >/dev/null 2>&1; then + echo "error: required command not found: mktemp" >&2 + exit 1 + fi + + helper_file="$(mktemp)" + curl -fsSL "https://raw.githubusercontent.com/${repo}/${ref}/install.sh" -o "$helper_file" + # shellcheck source=/dev/null + . "$helper_file" + rm -f "$helper_file" +} + +source_installer_helpers + +usage_runtime() { + cat < 0)); do + case "$1" in + --pre-release) + INSTALL_PRERELEASE=1 + ;; + --stable) + INSTALL_PRERELEASE=0 + ;; + --service) + INSTALL_SERVICE=1 + ;; + --service-args) + echo "error: background services now run \`mesh-llm serve\` and load startup models from $MESH_CONFIG_FILE" >&2 + echo "Add startup models under [[models]] instead of passing custom service args." >&2 + exit 1 + ;; + --no-start-service) + INSTALL_SERVICE_START=0 + ;; + -h|--help) + usage_runtime + exit 0 + ;; + *) + echo "error: unknown argument: $1" >&2 + echo >&2 + usage_runtime >&2 + exit 1 + ;; + esac + shift + done +} + +download_native_runtime_manifest() { + local tmp_dir="$1" + local manifest_path="$tmp_dir/native-runtimes.json" + local manifest_url + + if [[ -f "$manifest_path" ]]; then + return 0 + fi + if ! manifest_url="$(release_url "native-runtimes.json")"; then + return 1 + fi + + echo "Downloading $manifest_url" + curl -fsSL "$manifest_url" -o "$manifest_path" 2>/dev/null +} + +download_release_asset() { + local asset="$1" + local archive="$2" + local url + + if ! url="$(release_url "$asset")"; then + return 1 + fi + + echo "Downloading $url" + curl -fsSL "$url" -o "$archive" 2>/dev/null +} + +download_runtime_binary_archive() { + local tmp_dir="$1" + local requested_asset="$2" + local requested_archive="$tmp_dir/$requested_asset" + local fallback_asset="mesh-bundle.tar.gz" + local fallback_archive="$tmp_dir/$fallback_asset" + + DOWNLOADED_ASSET="$requested_asset" + DOWNLOADED_ARCHIVE="$requested_archive" + + if download_release_asset "$requested_asset" "$requested_archive"; then + return 0 + fi + + if [[ "$requested_asset" != "$fallback_asset" ]] && + download_release_asset "$fallback_asset" "$fallback_archive"; then + echo "Using runtime-enabled mesh bundle because $requested_asset was not available." + DOWNLOADED_ASSET="$fallback_asset" + DOWNLOADED_ARCHIVE="$fallback_archive" + return 0 + fi + + echo "error: could not download runtime release archive: $requested_asset or $fallback_asset" >&2 + return 1 +} + +install_native_runtime_required() { + local tmp_dir="$1" + local manifest_path="$tmp_dir/native-runtimes.json" + local binary="$INSTALL_DIR/mesh-llm" + + if [[ ! -x "$binary" ]]; then + echo "error: mesh-llm binary was not installed at $binary" >&2 + return 1 + fi + if ! "$binary" runtime install --help >/dev/null 2>&1; then + echo "error: installed mesh-llm does not support native runtime install" >&2 + return 1 + fi + if [[ ! -f "$manifest_path" ]]; then + echo "error: native runtime manifest was not downloaded" >&2 + return 1 + fi + + "$binary" runtime install --manifest "$manifest_path" + "$binary" runtime prune --active-only || true +} + +main_runtime() { + parse_runtime_args "$@" + if [[ -n "$INSTALL_SERVICE_ARGS" ]]; then + echo "error: background services now run \`mesh-llm serve\` and load startup models from $MESH_CONFIG_FILE" >&2 + echo "Add startup models under [[models]] instead of using MESH_LLM_INSTALL_SERVICE_ARGS." >&2 + exit 1 + fi + need_cmd curl + need_cmd tar + need_cmd mktemp + + local flavor + flavor="$(choose_flavor)" + local asset + asset="$(asset_name "$flavor")" + + local tmp_dir + tmp_dir="$(mktemp -d)" + local tmp_dir_escaped + printf -v tmp_dir_escaped '%q' "$tmp_dir" + trap "rm -rf -- $tmp_dir_escaped" EXIT + + echo "Installing runtime flavor: $flavor" + if bool_is_true "$INSTALL_PRERELEASE"; then + echo "Release channel: prerelease" + else + echo "Release channel: stable" + fi + + if ! download_native_runtime_manifest "$tmp_dir"; then + echo "error: native runtime manifest was not available for this release." >&2 + echo "Use install.sh for bundled-runtime stable releases, or retry with --pre-release." >&2 + exit 1 + fi + download_runtime_binary_archive "$tmp_dir" "$asset" + + tar -xzf "$DOWNLOADED_ARCHIVE" -C "$tmp_dir" + + if [[ ! -d "$tmp_dir/mesh-bundle" ]]; then + echo "error: release archive did not contain mesh-bundle/" >&2 + exit 1 + fi + + install_bundle "$tmp_dir/mesh-bundle" + install_native_runtime_required "$tmp_dir" + + echo "Installed $DOWNLOADED_ASSET and native runtime to $INSTALL_DIR" + + if bool_is_true "$INSTALL_SERVICE"; then + echo + install_service + fi + + if ! path_contains_install_dir; then + echo + echo "$INSTALL_DIR is not on your PATH." + echo "Add it with one of these commands:" + echo + echo "bash:" + echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.bashrc" + echo " source ~/.bashrc" + echo + echo "zsh:" + echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.zshrc" + echo " source ~/.zshrc" + fi +} + +if [[ "${BASH_SOURCE[0]-}" == "$0" || ( -z "${BASH_SOURCE[0]-}" && "$0" == "bash" ) ]]; then + main_runtime "$@" +fi diff --git a/scripts/tests/test_install_runtime_sh.py b/scripts/tests/test_install_runtime_sh.py new file mode 100644 index 0000000000..3bda7ffef8 --- /dev/null +++ b/scripts/tests/test_install_runtime_sh.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +import io +import os +from pathlib import Path +import subprocess +import tarfile +import tempfile +import textwrap +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +SCRIPT = ROOT / "install-runtime.sh" + + +class InstallRuntimeScriptTests(unittest.TestCase): + def test_defaults_to_prerelease_channel(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + result = self._run_helper( + Path(tmp), + """ + printf '%s\\n' "$INSTALL_PRERELEASE" + """, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), "1") + + def test_download_runtime_binary_archive_prefers_platform_bundle(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + assets_dir = tmp_path / "assets" + assets_dir.mkdir() + platform_asset = "mesh-llm-aarch64-apple-darwin.tar.gz" + (assets_dir / platform_asset).write_text("platform\n", encoding="utf-8") + (assets_dir / "mesh-bundle.tar.gz").write_text("fallback\n", encoding="utf-8") + + result = self._run_helper( + tmp_path, + f""" + release_url() {{ + printf 'file://{assets_dir}/%s\\n' "$1" + }} + download_runtime_binary_archive "{tmp_path}" "{platform_asset}" + printf 'asset=%s\\narchive=%s\\n' "$DOWNLOADED_ASSET" "$DOWNLOADED_ARCHIVE" + """, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn(f"asset={platform_asset}", result.stdout) + self.assertIn(f"archive={tmp_path / platform_asset}", result.stdout) + + def test_download_runtime_binary_archive_falls_back_to_mesh_bundle(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + assets_dir = tmp_path / "assets" + assets_dir.mkdir() + platform_asset = "mesh-llm-aarch64-apple-darwin.tar.gz" + (assets_dir / "mesh-bundle.tar.gz").write_text("fallback\n", encoding="utf-8") + + result = self._run_helper( + tmp_path, + f""" + release_url() {{ + printf 'file://{assets_dir}/%s\\n' "$1" + }} + download_runtime_binary_archive "{tmp_path}" "{platform_asset}" + printf 'asset=%s\\narchive=%s\\n' "$DOWNLOADED_ASSET" "$DOWNLOADED_ARCHIVE" + """, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("asset=mesh-bundle.tar.gz", result.stdout) + self.assertIn(f"archive={tmp_path / 'mesh-bundle.tar.gz'}", result.stdout) + self.assertIn("Using runtime-enabled mesh bundle", result.stdout) + + def test_install_native_runtime_required_rejects_old_binary(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + install_dir = tmp_path / "bin" + install_dir.mkdir() + (tmp_path / "native-runtimes.json").write_text("{}\n", encoding="utf-8") + self._write_fake_mesh_llm( + install_dir / "mesh-llm", + """ + exit 2 + """, + ) + + result = self._run_helper( + tmp_path, + f""" + INSTALL_DIR={install_dir} + install_native_runtime_required "{tmp_path}" + """, + ) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("does not support native runtime install", result.stderr) + + def test_main_runtime_installs_mesh_bundle_and_required_runtime(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + install_dir = tmp_path / "bin" + assets_dir = tmp_path / "assets" + assets_dir.mkdir() + self._write_release_archive(assets_dir / "mesh-bundle.tar.gz") + (assets_dir / "native-runtimes.json").write_text("{}\n", encoding="utf-8") + + result = self._run_helper( + tmp_path, + f""" + INSTALL_DIR={install_dir} + MESH_LLM_TEST_UNAME_S=Darwin + MESH_LLM_TEST_UNAME_M=arm64 + release_url() {{ + printf 'file://{assets_dir}/%s\\n' "$1" + }} + main_runtime + """, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + calls = (install_dir / "runtime-calls.log").read_text(encoding="utf-8") + self.assertIn("runtime install --manifest ", calls) + self.assertIn("native-runtimes.json", calls) + self.assertIn("runtime prune --active-only", calls) + self.assertIn("Installed mesh-bundle.tar.gz and native runtime", result.stdout) + + def _run_helper(self, tmp_path: Path, body: str) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + script = textwrap.dedent( + f""" + set -euo pipefail + source {SCRIPT} + {body} + """ + ) + return subprocess.run( + ["bash", "-c", script], + cwd=tmp_path, + env=env, + text=True, + capture_output=True, + check=False, + ) + + def _write_fake_mesh_llm(self, path: Path, body: str) -> None: + path.write_text( + "#!/usr/bin/env bash\nset -euo pipefail\n" + textwrap.dedent(body), + encoding="utf-8", + ) + path.chmod(0o755) + + def _write_release_archive(self, archive_path: Path) -> None: + script = textwrap.dedent( + """ + #!/usr/bin/env bash + set -euo pipefail + log="$(dirname "$0")/runtime-calls.log" + if [[ "$*" == "runtime install --help" ]]; then + exit 0 + fi + echo "$*" >> "$log" + exit 0 + """ + ) + data = script.encode("utf-8") + info = tarfile.TarInfo("mesh-bundle/mesh-llm") + info.mode = 0o755 + info.size = len(data) + + with tarfile.open(archive_path, "w:gz") as archive: + archive.addfile(info, io.BytesIO(data)) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/tests/test_install_sh.py b/scripts/tests/test_install_sh.py index f0fb354190..cd33125cd4 100644 --- a/scripts/tests/test_install_sh.py +++ b/scripts/tests/test_install_sh.py @@ -13,85 +13,22 @@ class InstallScriptTests(unittest.TestCase): - def test_download_release_archive_prefers_platform_bundle(self) -> None: + def test_legacy_installer_uses_platform_asset_name(self) -> None: with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) - install_dir = tmp_path / "bin" - install_dir.mkdir() - assets_dir = tmp_path / "assets" - assets_dir.mkdir() - platform_asset = "mesh-llm-aarch64-apple-darwin.tar.gz" - (assets_dir / platform_asset).write_text("platform\n", encoding="utf-8") - (assets_dir / "native-runtimes.json").write_text("{}\n", encoding="utf-8") - (assets_dir / "mesh-bundle.tar.gz").write_text("fallback\n", encoding="utf-8") - result = self._run_helper( tmp_path, - install_dir, - f""" - release_url() {{ - printf 'file://{assets_dir}/%s\\n' "$1" - }} - download_release_archive "{tmp_path}" "{platform_asset}" - printf 'asset=%s\\narchive=%s\\n' "$DOWNLOADED_ASSET" "$DOWNLOADED_ARCHIVE" - """, - ) - - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn(f"asset={platform_asset}", result.stdout) - self.assertIn(f"archive={tmp_path / platform_asset}", result.stdout) - - def test_download_release_archive_falls_back_to_runtime_mesh_bundle(self) -> None: - with tempfile.TemporaryDirectory() as tmp: - tmp_path = Path(tmp) - install_dir = tmp_path / "bin" - install_dir.mkdir() - assets_dir = tmp_path / "assets" - assets_dir.mkdir() - platform_asset = "mesh-llm-aarch64-apple-darwin.tar.gz" - (assets_dir / "native-runtimes.json").write_text("{}\n", encoding="utf-8") - (assets_dir / "mesh-bundle.tar.gz").write_text("fallback\n", encoding="utf-8") - - result = self._run_helper( - tmp_path, - install_dir, - f""" - release_url() {{ - printf 'file://{assets_dir}/%s\\n' "$1" - }} - download_release_archive "{tmp_path}" "{platform_asset}" - printf 'asset=%s\\narchive=%s\\n' "$DOWNLOADED_ASSET" "$DOWNLOADED_ARCHIVE" + """ + MESH_LLM_TEST_UNAME_S=Darwin + MESH_LLM_TEST_UNAME_M=arm64 + asset_name metal """, ) self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn("asset=mesh-bundle.tar.gz", result.stdout) - self.assertIn(f"archive={tmp_path / 'mesh-bundle.tar.gz'}", result.stdout) - self.assertIn("Using runtime-enabled mesh bundle", result.stdout) + self.assertEqual(result.stdout.strip(), "mesh-llm-aarch64-apple-darwin.tar.gz") - def test_download_release_archive_fails_without_old_or_new_release_shape(self) -> None: - with tempfile.TemporaryDirectory() as tmp: - tmp_path = Path(tmp) - install_dir = tmp_path / "bin" - install_dir.mkdir() - assets_dir = tmp_path / "assets" - assets_dir.mkdir() - - result = self._run_helper( - tmp_path, - install_dir, - f""" - release_url() {{ - printf 'file://{assets_dir}/%s\\n' "$1" - }} - download_release_archive "{tmp_path}" "mesh-llm-aarch64-apple-darwin.tar.gz" - """, - ) - - self.assertNotEqual(result.returncode, 0) - self.assertIn("could not download release archive", result.stderr) - - def test_missing_native_runtime_manifest_is_silent_and_optional(self) -> None: + def test_legacy_installer_still_treats_runtime_manifest_as_optional(self) -> None: with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) install_dir = tmp_path / "bin" @@ -100,9 +37,6 @@ def test_missing_native_runtime_manifest_is_silent_and_optional(self) -> None: self._write_fake_mesh_llm( install_dir / "mesh-llm", f""" - if [[ "$*" == "runtime install --help" ]]; then - exit 0 - fi echo "$*" >> {calls} exit 0 """, @@ -110,8 +44,8 @@ def test_missing_native_runtime_manifest_is_silent_and_optional(self) -> None: result = self._run_helper( tmp_path, - install_dir, f""" + INSTALL_DIR={install_dir} release_url() {{ printf 'file://{tmp_path}/missing-native-runtimes.json\\n' }} @@ -120,91 +54,15 @@ def test_missing_native_runtime_manifest_is_silent_and_optional(self) -> None: ) self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, "") - self.assertEqual(result.stderr, "") + self.assertIn("Native runtime manifest was not available", result.stdout) self.assertFalse(calls.exists()) - def test_old_binary_without_runtime_command_skips_manifest_lookup(self) -> None: - with tempfile.TemporaryDirectory() as tmp: - tmp_path = Path(tmp) - install_dir = tmp_path / "bin" - install_dir.mkdir() - release_url_calls = tmp_path / "release-url-calls.log" - self._write_fake_mesh_llm( - install_dir / "mesh-llm", - """ - exit 2 - """, - ) - - result = self._run_helper( - tmp_path, - install_dir, - f""" - release_url() {{ - echo called >> {release_url_calls} - return 1 - }} - install_recommended_native_runtime "{tmp_path}" - """, - ) - - self.assertEqual(result.returncode, 0, result.stderr) - self.assertFalse(release_url_calls.exists()) - - def test_runtime_capable_binary_installs_available_manifest(self) -> None: - with tempfile.TemporaryDirectory() as tmp: - tmp_path = Path(tmp) - install_dir = tmp_path / "bin" - install_dir.mkdir() - manifest = tmp_path / "native-runtimes-source.json" - manifest.write_text('{"runtimes":[]}\n', encoding="utf-8") - calls = tmp_path / "calls.log" - self._write_fake_mesh_llm( - install_dir / "mesh-llm", - f""" - if [[ "$*" == "runtime install --help" ]]; then - exit 0 - fi - echo "$*" >> {calls} - exit 0 - """, - ) - - result = self._run_helper( - tmp_path, - install_dir, - f""" - release_url() {{ - printf 'file://{manifest}\\n' - }} - install_recommended_native_runtime "{tmp_path}" - """, - ) - - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn( - f"runtime install --manifest {tmp_path / 'native-runtimes.json'}", - calls.read_text(encoding="utf-8"), - ) - self.assertIn( - "runtime prune --active-only", - calls.read_text(encoding="utf-8"), - ) - - def _run_helper( - self, - tmp_path: Path, - install_dir: Path, - body: str, - ) -> subprocess.CompletedProcess[str]: + def _run_helper(self, tmp_path: Path, body: str) -> subprocess.CompletedProcess[str]: env = os.environ.copy() - env["INSTALL_DIR"] = str(install_dir) script = textwrap.dedent( f""" set -euo pipefail source {SCRIPT} - INSTALL_DIR={install_dir} {body} """ ) From 515609fe4a88fbed67c231d9173f1ddc66942579 Mon Sep 17 00:00:00 2001 From: James Dumay Date: Thu, 4 Jun 2026 09:14:25 +1000 Subject: [PATCH 2/7] self install native runtime on startup --- .../mesh-llm-host-runtime/src/runtime/mod.rs | 47 ++++++++++++++++++ .../src/system/native_runtime.rs | 48 +++++++++++++++++-- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/crates/mesh-llm-host-runtime/src/runtime/mod.rs b/crates/mesh-llm-host-runtime/src/runtime/mod.rs index 0cf502ba1f..dc4e549eba 100644 --- a/crates/mesh-llm-host-runtime/src/runtime/mod.rs +++ b/crates/mesh-llm-host-runtime/src/runtime/mod.rs @@ -3573,6 +3573,7 @@ async fn run_runtime_cli( let cli_has_explicit_models = cli_has_explicit_models(&options); let has_config_models = !config.models.is_empty(); let has_startup_models = cli_has_explicit_models || has_config_models; + maybe_prepare_native_runtime(&options, has_startup_models).await?; // Acquire the per-instance runtime directory and flock. Plain --client still // skips this, but capture observers register so detached runs can be found @@ -3952,6 +3953,30 @@ fn cli_has_explicit_models(options: &RuntimeOptions) -> bool { !options.model.is_empty() || !options.gguf.is_empty() } +fn should_prepare_native_runtime(options: &RuntimeOptions, has_startup_models: bool) -> bool { + has_startup_models || !options.client +} + +async fn maybe_prepare_native_runtime( + options: &RuntimeOptions, + has_startup_models: bool, +) -> Result<()> { + if !should_prepare_native_runtime(options, has_startup_models) { + return Ok(()); + } + #[cfg(feature = "dynamic-native-runtime")] + if let Some(runtime) = + crate::system::native_runtime::ensure_native_runtime_installed_and_loaded().await? + { + tracing::info!( + native_runtime_id = %runtime.native_runtime_id, + libraries = ?runtime.libraries, + "Loaded MeshLLM native runtime" + ); + } + Ok(()) +} + fn build_startup_model_specs( options: &RuntimeOptions, config: &plugin::MeshConfig, @@ -11118,6 +11143,28 @@ mod tests { assert!(!swarm_capture_observer_requested(&options)); } + #[test] + fn client_without_local_models_skips_native_runtime_prepare() { + let options = make_runtime_cli(&["mesh-llm", "client", "--auto"]); + + assert!(!should_prepare_native_runtime(&options, false)); + } + + #[test] + fn serving_runtime_prepares_native_runtime_without_startup_models() { + let options = make_runtime_cli(&["mesh-llm", "serve"]); + + assert!(should_prepare_native_runtime(&options, false)); + } + + #[test] + fn client_with_startup_models_prepares_native_runtime() { + let options = + make_runtime_cli(&["mesh-llm", "client", "--model", "hf://org/model/model.gguf"]); + + assert!(should_prepare_native_runtime(&options, true)); + } + #[test] #[serial] fn swarm_capture_env_client_registers_runtime_owner() { 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..6476d041b1 100644 --- a/crates/mesh-llm-host-runtime/src/system/native_runtime.rs +++ b/crates/mesh-llm-host-runtime/src/system/native_runtime.rs @@ -5,6 +5,10 @@ mod dynamic { HostRuntimeProfile, NativeRuntimeCache, NativeRuntimeReleaseManifest, RuntimeSelection, select_native_runtime, }; + use mesh_llm_runtime_install::{ + NativeRuntimeInstallOptions, NativeRuntimeInstallStatus, current_skippy_abi_version, + install_native_runtime, + }; use std::path::PathBuf; #[derive(Clone, Debug)] @@ -19,13 +23,11 @@ mod dynamic { } let cache = default_native_runtime_cache()?; let installed = cache.installed()?; + let skippy_abi = current_skippy_abi_version(); let profile = host_runtime_profile(); let manifest = NativeRuntimeReleaseManifest { mesh_version: crate::VERSION.to_string(), - skippy_abi: installed - .first() - .map(|runtime| runtime.manifest.runtime.skippy_abi.clone()) - .unwrap_or_default(), + skippy_abi, artifacts: installed .iter() .map(|runtime| runtime.manifest.runtime.clone()) @@ -63,6 +65,44 @@ mod dynamic { })) } + pub(crate) async fn ensure_native_runtime_installed_and_loaded() + -> Result> { + if let Some(runtime) = try_load_installed_native_runtime()? { + return Ok(Some(runtime)); + } + if skippy_runtime::native_runtime_loaded() { + return Ok(None); + } + + let outcome = install_native_runtime(NativeRuntimeInstallOptions::default()) + .await + .context("install MeshLLM native runtime from release manifest")?; + match outcome.status { + NativeRuntimeInstallStatus::AlreadyInstalled => { + tracing::info!( + native_runtime_id = %outcome.runtime.native_runtime_id, + "MeshLLM native runtime was already installed" + ); + } + NativeRuntimeInstallStatus::Installed => { + tracing::info!( + native_runtime_id = %outcome.runtime.native_runtime_id, + path = %outcome.runtime.path.display(), + "Installed MeshLLM native runtime" + ); + } + } + + try_load_installed_native_runtime()? + .with_context(|| { + format!( + "installed native runtime {} but could not load it", + outcome.runtime.native_runtime_id + ) + }) + .map(Some) + } + fn default_native_runtime_cache() -> Result { crate::system::native_runtime_install::default_native_runtime_cache() } From 9ab4eaef40cd02fadaff5e9900e83a76374033af Mon Sep 17 00:00:00 2001 From: James Dumay Date: Thu, 4 Jun 2026 09:19:14 +1000 Subject: [PATCH 3/7] make install script runtime first --- install-runtime.sh | 229 +---------------------- install.sh | 182 +++++++++++++++--- scripts/tests/test_install_runtime_sh.py | 2 +- scripts/tests/test_install_sh.py | 42 ++--- 4 files changed, 185 insertions(+), 270 deletions(-) diff --git a/install-runtime.sh b/install-runtime.sh index 9bde98014b..35ca703f59 100755 --- a/install-runtime.sh +++ b/install-runtime.sh @@ -2,19 +2,10 @@ set -euo pipefail -# Runtime releases currently live on the prerelease channel. Keep install.sh as -# the stable bundled-runtime installer and make this script runtime-first. -MESH_LLM_INSTALL_PRERELEASE="${MESH_LLM_INSTALL_PRERELEASE:-1}" - -source_installer_helpers() { - if [[ -n "${MESH_LLM_RUNTIME_INSTALL_HELPERS:-}" ]]; then - # shellcheck source=/dev/null - . "$MESH_LLM_RUNTIME_INSTALL_HELPERS" - return 0 - fi - +source_runtime_installer() { local source_path="${BASH_SOURCE[0]-}" local script_dir + if [[ -n "$source_path" && "$source_path" == */* ]]; then script_dir="$(cd "$(dirname "$source_path")" && pwd)" if [[ -f "$script_dir/install.sh" ]]; then @@ -26,7 +17,7 @@ source_installer_helpers() { local repo="${MESH_LLM_INSTALL_REPO:-Mesh-LLM/mesh-llm}" local ref="${MESH_LLM_INSTALL_REF:-main}" - local helper_file + local installer_file if ! command -v curl >/dev/null 2>&1; then echo "error: required command not found: curl" >&2 @@ -37,217 +28,15 @@ source_installer_helpers() { exit 1 fi - helper_file="$(mktemp)" - curl -fsSL "https://raw.githubusercontent.com/${repo}/${ref}/install.sh" -o "$helper_file" + installer_file="$(mktemp)" + curl -fsSL "https://raw.githubusercontent.com/${repo}/${ref}/install.sh" -o "$installer_file" # shellcheck source=/dev/null - . "$helper_file" - rm -f "$helper_file" + . "$installer_file" + rm -f "$installer_file" } -source_installer_helpers - -usage_runtime() { - cat < 0)); do - case "$1" in - --pre-release) - INSTALL_PRERELEASE=1 - ;; - --stable) - INSTALL_PRERELEASE=0 - ;; - --service) - INSTALL_SERVICE=1 - ;; - --service-args) - echo "error: background services now run \`mesh-llm serve\` and load startup models from $MESH_CONFIG_FILE" >&2 - echo "Add startup models under [[models]] instead of passing custom service args." >&2 - exit 1 - ;; - --no-start-service) - INSTALL_SERVICE_START=0 - ;; - -h|--help) - usage_runtime - exit 0 - ;; - *) - echo "error: unknown argument: $1" >&2 - echo >&2 - usage_runtime >&2 - exit 1 - ;; - esac - shift - done -} - -download_native_runtime_manifest() { - local tmp_dir="$1" - local manifest_path="$tmp_dir/native-runtimes.json" - local manifest_url - - if [[ -f "$manifest_path" ]]; then - return 0 - fi - if ! manifest_url="$(release_url "native-runtimes.json")"; then - return 1 - fi - - echo "Downloading $manifest_url" - curl -fsSL "$manifest_url" -o "$manifest_path" 2>/dev/null -} - -download_release_asset() { - local asset="$1" - local archive="$2" - local url - - if ! url="$(release_url "$asset")"; then - return 1 - fi - - echo "Downloading $url" - curl -fsSL "$url" -o "$archive" 2>/dev/null -} - -download_runtime_binary_archive() { - local tmp_dir="$1" - local requested_asset="$2" - local requested_archive="$tmp_dir/$requested_asset" - local fallback_asset="mesh-bundle.tar.gz" - local fallback_archive="$tmp_dir/$fallback_asset" - - DOWNLOADED_ASSET="$requested_asset" - DOWNLOADED_ARCHIVE="$requested_archive" - - if download_release_asset "$requested_asset" "$requested_archive"; then - return 0 - fi - - if [[ "$requested_asset" != "$fallback_asset" ]] && - download_release_asset "$fallback_asset" "$fallback_archive"; then - echo "Using runtime-enabled mesh bundle because $requested_asset was not available." - DOWNLOADED_ASSET="$fallback_asset" - DOWNLOADED_ARCHIVE="$fallback_archive" - return 0 - fi - - echo "error: could not download runtime release archive: $requested_asset or $fallback_asset" >&2 - return 1 -} - -install_native_runtime_required() { - local tmp_dir="$1" - local manifest_path="$tmp_dir/native-runtimes.json" - local binary="$INSTALL_DIR/mesh-llm" - - if [[ ! -x "$binary" ]]; then - echo "error: mesh-llm binary was not installed at $binary" >&2 - return 1 - fi - if ! "$binary" runtime install --help >/dev/null 2>&1; then - echo "error: installed mesh-llm does not support native runtime install" >&2 - return 1 - fi - if [[ ! -f "$manifest_path" ]]; then - echo "error: native runtime manifest was not downloaded" >&2 - return 1 - fi - - "$binary" runtime install --manifest "$manifest_path" - "$binary" runtime prune --active-only || true -} - -main_runtime() { - parse_runtime_args "$@" - if [[ -n "$INSTALL_SERVICE_ARGS" ]]; then - echo "error: background services now run \`mesh-llm serve\` and load startup models from $MESH_CONFIG_FILE" >&2 - echo "Add startup models under [[models]] instead of using MESH_LLM_INSTALL_SERVICE_ARGS." >&2 - exit 1 - fi - need_cmd curl - need_cmd tar - need_cmd mktemp - - local flavor - flavor="$(choose_flavor)" - local asset - asset="$(asset_name "$flavor")" - - local tmp_dir - tmp_dir="$(mktemp -d)" - local tmp_dir_escaped - printf -v tmp_dir_escaped '%q' "$tmp_dir" - trap "rm -rf -- $tmp_dir_escaped" EXIT - - echo "Installing runtime flavor: $flavor" - if bool_is_true "$INSTALL_PRERELEASE"; then - echo "Release channel: prerelease" - else - echo "Release channel: stable" - fi - - if ! download_native_runtime_manifest "$tmp_dir"; then - echo "error: native runtime manifest was not available for this release." >&2 - echo "Use install.sh for bundled-runtime stable releases, or retry with --pre-release." >&2 - exit 1 - fi - download_runtime_binary_archive "$tmp_dir" "$asset" - - tar -xzf "$DOWNLOADED_ARCHIVE" -C "$tmp_dir" - - if [[ ! -d "$tmp_dir/mesh-bundle" ]]; then - echo "error: release archive did not contain mesh-bundle/" >&2 - exit 1 - fi - - install_bundle "$tmp_dir/mesh-bundle" - install_native_runtime_required "$tmp_dir" - - echo "Installed $DOWNLOADED_ASSET and native runtime to $INSTALL_DIR" - - if bool_is_true "$INSTALL_SERVICE"; then - echo - install_service - fi - - if ! path_contains_install_dir; then - echo - echo "$INSTALL_DIR is not on your PATH." - echo "Add it with one of these commands:" - echo - echo "bash:" - echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.bashrc" - echo " source ~/.bashrc" - echo - echo "zsh:" - echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.zshrc" - echo " source ~/.zshrc" - fi -} +source_runtime_installer if [[ "${BASH_SOURCE[0]-}" == "$0" || ( -z "${BASH_SOURCE[0]-}" && "$0" == "bash" ) ]]; then - main_runtime "$@" + main "$@" fi diff --git a/install.sh b/install.sh index 170585dcf4..fade6859c4 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ REPO="${MESH_LLM_INSTALL_REPO:-Mesh-LLM/mesh-llm}" REPO_REF="${MESH_LLM_INSTALL_REF:-main}" INSTALL_DIR="${MESH_LLM_INSTALL_DIR:-$HOME/.local/bin}" INSTALL_FLAVOR="${MESH_LLM_INSTALL_FLAVOR:-}" -INSTALL_PRERELEASE="${MESH_LLM_INSTALL_PRERELEASE:-0}" +INSTALL_PRERELEASE="${MESH_LLM_INSTALL_PRERELEASE:-1}" INSTALL_SERVICE="${MESH_LLM_INSTALL_SERVICE:-0}" INSTALL_SERVICE_ARGS="${MESH_LLM_INSTALL_SERVICE_ARGS:-}" INSTALL_SERVICE_START="${MESH_LLM_INSTALL_SERVICE_START:-1}" @@ -53,10 +53,11 @@ bool_is_true() { usage() { cat </dev/null)"; then + echo "error: could not query latest GitHub release for ${REPO}" >&2 + return 1 + fi + + local compact + local tag + compact="$(printf '%s' "$response" | tr -d '\n\r\t ')" + tag="$( + printf '%s' "$compact" | + awk ' + { + if (match($0, /"tag_name":"[^"]+"/)) { + value = substr($0, RSTART, RLENGTH) + sub(/^"tag_name":"/, "", value) + sub(/"$/, "", value) + print value + } + } + ' + )" + + if [[ -z "$tag" ]]; then + echo "error: could not find latest release tag for ${REPO}" >&2 + return 1 + fi + + printf '%s\n' "$tag" +} + +versioned_release_asset_name() { + local asset="$1" + local tag="$2" + + case "$asset" in + native-runtimes.json) + printf '%s\n' "$asset" + ;; + mesh-llm-v[0-9]*) + printf '%s\n' "$asset" + ;; + mesh-llm-*) + printf 'mesh-llm-%s-%s\n' "$tag" "${asset#mesh-llm-}" + ;; + *) + printf '%s\n' "$asset" + ;; + esac +} + release_url() { local asset="$1" + local tag if bool_is_true "$INSTALL_PRERELEASE"; then - local tag if ! tag="$(latest_prerelease_tag)"; then return 1 fi - printf 'https://github.com/%s/releases/download/%s/%s\n' "$REPO" "$tag" "$asset" - return 0 + else + if ! tag="$(latest_stable_tag)"; then + return 1 + fi fi - printf 'https://github.com/%s/releases/latest/download/%s\n' "$REPO" "$asset" + printf 'https://github.com/%s/releases/download/%s/%s\n' \ + "$REPO" "$tag" "$(versioned_release_asset_name "$asset" "$tag")" } stale_binary_names() { @@ -633,27 +706,80 @@ install_bundle() { done } -install_recommended_native_runtime() { +download_native_runtime_manifest() { local tmp_dir="$1" - local manifest_url local manifest_path="$tmp_dir/native-runtimes.json" - local binary="$INSTALL_DIR/mesh-llm" + local manifest_url - if [[ ! -x "$binary" ]]; then + if [[ -f "$manifest_path" ]]; then return 0 fi if ! manifest_url="$(release_url "native-runtimes.json")"; then - return 0 + return 1 fi - if ! curl -fsSL "$manifest_url" -o "$manifest_path"; then - echo "Native runtime manifest was not available for this release; skipping runtime install." + + echo "Downloading $manifest_url" + curl -fsSL "$manifest_url" -o "$manifest_path" 2>/dev/null +} + +download_release_asset() { + local asset="$1" + local archive="$2" + local url + + if ! url="$(release_url "$asset")"; then + return 1 + fi + + echo "Downloading $url" + curl -fsSL "$url" -o "$archive" 2>/dev/null +} + +download_runtime_binary_archive() { + local tmp_dir="$1" + local requested_asset="$2" + local requested_archive="$tmp_dir/$requested_asset" + local fallback_asset="mesh-bundle.tar.gz" + local fallback_archive="$tmp_dir/$fallback_asset" + + DOWNLOADED_ASSET="$requested_asset" + DOWNLOADED_ARCHIVE="$requested_archive" + + if download_release_asset "$requested_asset" "$requested_archive"; then return 0 fi - "$binary" runtime install --manifest "$manifest_path" || { - echo "warning: native runtime install did not complete successfully." >&2 + if [[ "$requested_asset" != "$fallback_asset" ]] && + download_release_asset "$fallback_asset" "$fallback_archive"; then + echo "Using runtime-enabled mesh bundle because $requested_asset was not available." + DOWNLOADED_ASSET="$fallback_asset" + DOWNLOADED_ARCHIVE="$fallback_archive" return 0 - } + fi + + echo "error: could not download runtime release archive: $requested_asset or $fallback_asset" >&2 + return 1 +} + +install_native_runtime_required() { + local tmp_dir="$1" + local manifest_path="$tmp_dir/native-runtimes.json" + local binary="$INSTALL_DIR/mesh-llm" + + if [[ ! -x "$binary" ]]; then + echo "error: mesh-llm binary was not installed at $binary" >&2 + return 1 + fi + if ! "$binary" runtime install --help >/dev/null 2>&1; then + echo "error: installed mesh-llm does not support native runtime install" >&2 + return 1 + fi + if [[ ! -f "$manifest_path" ]]; then + echo "error: native runtime manifest was not downloaded" >&2 + return 1 + fi + + "$binary" runtime install --manifest "$manifest_path" "$binary" runtime prune --active-only || true } @@ -898,10 +1024,6 @@ main() { flavor="$(choose_flavor)" local asset asset="$(asset_name "$flavor")" - local url - if ! url="$(release_url "$asset")"; then - exit 1 - fi local tmp_dir tmp_dir="$(mktemp -d)" @@ -909,17 +1031,21 @@ main() { printf -v tmp_dir_escaped '%q' "$tmp_dir" trap "rm -rf -- $tmp_dir_escaped" EXIT - local archive="$tmp_dir/$asset" echo "Installing flavor: $flavor" if bool_is_true "$INSTALL_PRERELEASE"; then echo "Release channel: prerelease" else echo "Release channel: stable" fi - echo "Downloading $url" - curl -fsSL "$url" -o "$archive" - tar -xzf "$archive" -C "$tmp_dir" + if ! download_native_runtime_manifest "$tmp_dir"; then + echo "error: native runtime manifest was not available for this release." >&2 + echo "Retry with --pre-release, or wait for stable to publish runtime release assets." >&2 + exit 1 + fi + download_runtime_binary_archive "$tmp_dir" "$asset" + + tar -xzf "$DOWNLOADED_ARCHIVE" -C "$tmp_dir" if [[ ! -d "$tmp_dir/mesh-bundle" ]]; then echo "error: release archive did not contain mesh-bundle/" >&2 @@ -927,9 +1053,9 @@ main() { fi install_bundle "$tmp_dir/mesh-bundle" - install_recommended_native_runtime "$tmp_dir" + install_native_runtime_required "$tmp_dir" - echo "Installed $asset to $INSTALL_DIR" + echo "Installed $DOWNLOADED_ASSET and native runtime to $INSTALL_DIR" if bool_is_true "$INSTALL_SERVICE"; then echo diff --git a/scripts/tests/test_install_runtime_sh.py b/scripts/tests/test_install_runtime_sh.py index 3bda7ffef8..284c24c35c 100644 --- a/scripts/tests/test_install_runtime_sh.py +++ b/scripts/tests/test_install_runtime_sh.py @@ -117,7 +117,7 @@ def test_main_runtime_installs_mesh_bundle_and_required_runtime(self) -> None: release_url() {{ printf 'file://{assets_dir}/%s\\n' "$1" }} - main_runtime + main """, ) diff --git a/scripts/tests/test_install_sh.py b/scripts/tests/test_install_sh.py index cd33125cd4..99df3241a0 100644 --- a/scripts/tests/test_install_sh.py +++ b/scripts/tests/test_install_sh.py @@ -13,7 +13,19 @@ class InstallScriptTests(unittest.TestCase): - def test_legacy_installer_uses_platform_asset_name(self) -> None: + def test_defaults_to_prerelease_channel(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + result = self._run_helper( + Path(tmp), + """ + printf '%s\\n' "$INSTALL_PRERELEASE" + """, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), "1") + + def test_runtime_installer_uses_platform_asset_name(self) -> None: with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) result = self._run_helper( @@ -28,34 +40,22 @@ def test_legacy_installer_uses_platform_asset_name(self) -> None: self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(result.stdout.strip(), "mesh-llm-aarch64-apple-darwin.tar.gz") - def test_legacy_installer_still_treats_runtime_manifest_as_optional(self) -> None: + def test_runtime_installer_requires_native_runtime_manifest(self) -> None: with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) - install_dir = tmp_path / "bin" - install_dir.mkdir() - calls = tmp_path / "calls.log" - self._write_fake_mesh_llm( - install_dir / "mesh-llm", - f""" - echo "$*" >> {calls} - exit 0 - """, - ) result = self._run_helper( tmp_path, - f""" - INSTALL_DIR={install_dir} - release_url() {{ - printf 'file://{tmp_path}/missing-native-runtimes.json\\n' - }} - install_recommended_native_runtime "{tmp_path}" + """ + release_url() { + printf 'file:///missing/%s\\n' "$1" + } + main """, ) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn("Native runtime manifest was not available", result.stdout) - self.assertFalse(calls.exists()) + self.assertNotEqual(result.returncode, 0) + self.assertIn("native runtime manifest was not available", result.stderr) def _run_helper(self, tmp_path: Path, body: str) -> subprocess.CompletedProcess[str]: env = os.environ.copy() From cf73d60deeeccfac92bf829977cdae82297bac88 Mon Sep 17 00:00:00 2001 From: James Dumay Date: Thu, 4 Jun 2026 09:34:13 +1000 Subject: [PATCH 4/7] publish native runtimes for release targets --- .github/workflows/release.yml | 143 ++++++++++++++++++---------------- scripts/package-release.ps1 | 2 - scripts/package-release.sh | 1 - 3 files changed, 78 insertions(+), 68 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b68cfc3290..76956439d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,12 +87,14 @@ jobs: build_recipe: release-build bundle_recipe: release-bundle backend: metal + target: aarch64-apple-darwin - name: Linux x86_64 CPU os: ubuntu-24.04 artifact_name: release-linux build_recipe: release-build bundle_recipe: release-bundle backend: cpu + target: x86_64-unknown-linux-gnu env: LLAMA_STAGE_BACKEND: ${{ matrix.backend }} steps: @@ -142,6 +144,12 @@ jobs: printf '%s' "$RELEASE_ATTESTATION_PUBLIC_KEY" > "$MESH_RELEASE_ATTESTATION_PUBLIC_KEY_FILE" just --shell bash --shell-arg -c ${{ matrix.build_recipe }} just --shell bash --shell-arg -c ${{ matrix.bundle_recipe }} "$RELEASE_TAG" dist + scripts/package-native-runtime.sh \ + --build \ + --backend "${{ matrix.backend }}" \ + --target "${{ matrix.target }}" \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - name: Upload release bundle uses: actions/upload-artifact@v6 @@ -152,11 +160,13 @@ jobs: - name: Upload Linux smoke binary if: matrix.artifact_name == 'release-linux' + env: + RELEASE_TAG: ${{ needs.metadata.outputs.tag }} run: | set -euo pipefail tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT - tar -xzf dist/mesh-llm-x86_64-unknown-linux-gnu.tar.gz -C "$tmp_dir" + tar -xzf "dist/mesh-llm-${RELEASE_TAG}-x86_64-unknown-linux-gnu.tar.gz" -C "$tmp_dir" cp "$tmp_dir/mesh-bundle/mesh-llm" dist/mesh-llm - name: Upload Linux smoke binary artifact @@ -245,68 +255,6 @@ jobs: dist/native-sdk-crates/*/target/package/*.crate if-no-files-found: error - build_native_runtime: - name: Build native runtime ${{ matrix.name }} - needs: metadata - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - name: macOS aarch64 Metal - os: macos-15 - backend: metal - target: aarch64-apple-darwin - artifact_suffix: darwin-aarch64-metal - - name: Linux x86_64 CPU - os: ubuntu-24.04 - backend: cpu - target: x86_64-unknown-linux-gnu - artifact_suffix: linux-x86_64-cpu - env: - LLAMA_STAGE_BACKEND: ${{ matrix.backend }} - MESH_NATIVE_RUNTIME_TARGET: ${{ matrix.target }} - steps: - - uses: actions/checkout@v5 - - - uses: dtolnay/rust-toolchain@stable - - - uses: mozilla-actions/sccache-action@v0.0.9 - - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y build-essential cmake ninja-build pkg-config libssl-dev libdbus-1-dev curl lld - - - name: Install macOS dependencies - if: runner.os == 'macOS' - run: brew install cmake ninja lld - - - name: Prepare dispatched release version - if: github.event_name == 'workflow_dispatch' - env: - RELEASE_TAG: ${{ needs.metadata.outputs.tag }} - run: scripts/release-version.sh "$RELEASE_TAG" - - - name: Package native runtime - run: | - scripts/package-native-runtime.sh \ - --build \ - --backend "${{ matrix.backend }}" \ - --target "${{ matrix.target }}" \ - --out dist/native-runtimes - - - name: Verify native runtime artifact - run: scripts/verify-native-runtime-package.sh dist/native-runtimes/*.tar.gz - - - name: Upload native runtime - uses: actions/upload-artifact@v6 - with: - name: release-native-runtime-${{ matrix.artifact_suffix }} - path: | - dist/native-runtimes/*.tar.gz - dist/native-runtimes/*.sha256 - if-no-files-found: error - build_swift_sdk_artifact: name: Build Swift SDK XCFramework needs: metadata @@ -428,6 +376,12 @@ jobs: printf '%s' "$RELEASE_ATTESTATION_PUBLIC_KEY" > "$MESH_RELEASE_ATTESTATION_PUBLIC_KEY_FILE" just --shell bash --shell-arg -c release-build-aarch64 just --shell bash --shell-arg -c release-bundle-aarch64 "$RELEASE_TAG" dist + scripts/package-native-runtime.sh \ + --build \ + --backend cpu \ + --target aarch64-unknown-linux-gnu \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - uses: actions/upload-artifact@v6 with: name: release-linux-arm64 @@ -488,6 +442,14 @@ jobs: run: | just --shell bash --shell-arg -c release-build-aarch64-cuda just --shell bash --shell-arg -c release-bundle-aarch64-cuda "$RELEASE_TAG" dist + export MESH_LLM_CUDA_TOOLKIT_MAJOR="${MESH_CUDA_VERSION%%.*}" + export LLAMA_STAGE_CUDA_ARCHITECTURES="$(if [[ "${MESH_CUDA_VERSION:-}" == 13.* ]]; then echo '75;80;86;87;89;90;110'; else echo '75;80;86;87;89;90'; fi)" + scripts/package-native-runtime.sh \ + --build \ + --backend cuda \ + --target aarch64-unknown-linux-gnu \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - uses: actions/upload-artifact@v6 with: name: release-linux-aarch64-cuda-${{ matrix.cuda_version }} @@ -553,6 +515,14 @@ jobs: printf '%s' "$RELEASE_ATTESTATION_PUBLIC_KEY" > "$MESH_RELEASE_ATTESTATION_PUBLIC_KEY_FILE" just --shell bash --shell-arg -c release-build-cuda just --shell bash --shell-arg -c release-bundle-cuda "$RELEASE_TAG" dist + export MESH_LLM_CUDA_TOOLKIT_MAJOR="${MESH_CUDA_VERSION%%.*}" + export LLAMA_STAGE_CUDA_ARCHITECTURES="$(if [[ "${MESH_CUDA_VERSION:-}" == 13.* ]]; then echo '75;80;86;87;89;90;100;103;120;121'; else echo '75;80;86;87;89;90'; fi)" + scripts/package-native-runtime.sh \ + --build \ + --backend cuda \ + --target x86_64-unknown-linux-gnu \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - uses: actions/upload-artifact@v6 with: name: release-linux-cuda-${{ matrix.cuda_version }} @@ -615,6 +585,14 @@ jobs: printf '%s' "$RELEASE_ATTESTATION_PUBLIC_KEY" > "$MESH_RELEASE_ATTESTATION_PUBLIC_KEY_FILE" just --shell bash --shell-arg -c release-build-rocm just --shell bash --shell-arg -c release-bundle-rocm "$RELEASE_TAG" dist + export MESH_LLM_ROCM_VERSION=7.0 + export LLAMA_STAGE_AMDGPU_TARGETS='gfx90a;gfx942;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201' + scripts/package-native-runtime.sh \ + --build \ + --backend rocm \ + --target x86_64-unknown-linux-gnu \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - uses: actions/upload-artifact@v6 with: name: release-linux-rocm @@ -661,6 +639,12 @@ jobs: printf '%s' "$RELEASE_ATTESTATION_PUBLIC_KEY" > "$MESH_RELEASE_ATTESTATION_PUBLIC_KEY_FILE" just --shell bash --shell-arg -c release-build-vulkan just --shell bash --shell-arg -c release-bundle-vulkan "$RELEASE_TAG" dist + scripts/package-native-runtime.sh \ + --build \ + --backend vulkan \ + --target x86_64-unknown-linux-gnu \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - uses: actions/upload-artifact@v6 with: name: release-linux-vulkan @@ -708,6 +692,15 @@ jobs: Set-Content -Path $env:MESH_RELEASE_ATTESTATION_PUBLIC_KEY_FILE -Value $env:RELEASE_ATTESTATION_PUBLIC_KEY -NoNewline just release-build-windows just release-bundle-windows "$env:RELEASE_TAG" dist + - name: Package Windows CPU native runtime + shell: bash + run: | + scripts/package-native-runtime.sh \ + --build \ + --backend cpu \ + --target x86_64-pc-windows-msvc \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - uses: actions/upload-artifact@v6 with: name: release-windows @@ -814,6 +807,27 @@ jobs: Set-Content -Path $env:MESH_RELEASE_ATTESTATION_PUBLIC_KEY_FILE -Value $env:RELEASE_ATTESTATION_PUBLIC_KEY -NoNewline just ${{ matrix.build_recipe }} just ${{ matrix.bundle_recipe }} "$env:RELEASE_TAG" dist + - name: Package Windows native runtime + shell: bash + env: + BACKEND: ${{ matrix.backend }} + run: | + case "$BACKEND" in + cuda) + export MESH_LLM_CUDA_TOOLKIT_MAJOR="${WINDOWS_CUDA_VERSION%%.*}" + export LLAMA_STAGE_CUDA_ARCHITECTURES='75;80;86;87;89;90' + ;; + rocm) + export MESH_LLM_ROCM_VERSION=7.0 + export LLAMA_STAGE_AMDGPU_TARGETS='gfx90a;gfx942;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201' + ;; + esac + scripts/package-native-runtime.sh \ + --build \ + --backend "$BACKEND" \ + --target x86_64-pc-windows-msvc \ + --out dist + scripts/verify-native-runtime-package.sh dist/meshllm-native-runtime-*.tar.gz - uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_name }} @@ -827,7 +841,6 @@ jobs: - build - inference_smoke_tests - build_native_sdk_runtime - - build_native_runtime - build_swift_sdk_artifact - build_linux_arm64 - build_linux_aarch64_cuda @@ -836,7 +849,7 @@ jobs: - build_linux_vulkan - build_windows_cpu - build_windows_gpu - if: ${{ always() && needs.metadata.result == 'success' && needs.metadata.outputs.canary != 'true' && needs.build.result == 'success' && needs.inference_smoke_tests.result == 'success' && needs.build_native_sdk_runtime.result == 'success' && needs.build_native_runtime.result == 'success' && needs.build_swift_sdk_artifact.result == 'success' && needs.build_linux_arm64.result == 'success' && (needs.build_linux_aarch64_cuda.result == 'success' || needs.build_linux_aarch64_cuda.result == 'skipped') && (needs.build_linux_cuda.result == 'success' || needs.build_linux_cuda.result == 'skipped') && (needs.build_linux_rocm.result == 'success' || needs.build_linux_rocm.result == 'skipped') && (needs.build_linux_vulkan.result == 'success' || needs.build_linux_vulkan.result == 'skipped') && needs.build_windows_cpu.result == 'success' && (needs.build_windows_gpu.result == 'success' || needs.build_windows_gpu.result == 'skipped') }} + if: ${{ always() && needs.metadata.result == 'success' && needs.metadata.outputs.canary != 'true' && needs.build.result == 'success' && needs.inference_smoke_tests.result == 'success' && needs.build_native_sdk_runtime.result == 'success' && needs.build_swift_sdk_artifact.result == 'success' && needs.build_linux_arm64.result == 'success' && (needs.build_linux_aarch64_cuda.result == 'success' || needs.build_linux_aarch64_cuda.result == 'skipped') && (needs.build_linux_cuda.result == 'success' || needs.build_linux_cuda.result == 'skipped') && (needs.build_linux_rocm.result == 'success' || needs.build_linux_rocm.result == 'skipped') && (needs.build_linux_vulkan.result == 'success' || needs.build_linux_vulkan.result == 'skipped') && needs.build_windows_cpu.result == 'success' && (needs.build_windows_gpu.result == 'success' || needs.build_windows_gpu.result == 'skipped') }} runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 diff --git a/scripts/package-release.ps1 b/scripts/package-release.ps1 index 9f4428491a..084459ac45 100755 --- a/scripts/package-release.ps1 +++ b/scripts/package-release.ps1 @@ -401,10 +401,8 @@ try { Invoke-ReleaseAttestationStamp -BinaryPath $bundleBinary $versionedPath = Join-Path $resolvedOutputDir $versionedAsset - $stablePath = Join-Path $resolvedOutputDir $stableAsset New-ZipArchive -SourceDir $bundleDir -ArchivePath $versionedPath - New-ZipArchive -SourceDir $bundleDir -ArchivePath $stablePath Write-Host "Created release archives:" Get-ChildItem -Path $resolvedOutputDir -File | Sort-Object Name | ForEach-Object { diff --git a/scripts/package-release.sh b/scripts/package-release.sh index 1fb8f71d7e..fd19320c88 100755 --- a/scripts/package-release.sh +++ b/scripts/package-release.sh @@ -493,7 +493,6 @@ main() { stamp_bundle_binary "$bundle_binary" create_archive "$bundle_dir" "$output_dir/$versioned_asset" "$ARCHIVE_EXT" - create_archive "$bundle_dir" "$output_dir/$STABLE_ASSET" "$ARCHIVE_EXT" echo "Created release archives:" find "$output_dir" -maxdepth 1 -type f -print | sort From 1af538faeb3f535f49d14e994a080b0c458a8f2f Mon Sep 17 00:00:00 2001 From: James Dumay Date: Thu, 4 Jun 2026 10:10:10 +1000 Subject: [PATCH 5/7] unify sdk ffi with native runtime artifacts --- .github/workflows/release.yml | 69 +--- .../src/sdk/native_runtime.rs | 2 +- crates/mesh-llm-native-runtime/README.md | 9 +- crates/mesh-llm-native-runtime/src/cache.rs | 22 +- crates/mesh-llm-native-runtime/src/lib.rs | 2 +- .../mesh-llm-native-runtime/src/manifest.rs | 53 ++- .../mesh-llm-native-runtime/src/resolver.rs | 1 + crates/mesh-llm-runtime-install/src/lib.rs | 1 + docs/SDK.md | 10 +- docs/design/NATIVE_RUNTIMES.md | 2 + scripts/ci-kotlin-sdk-smoke.sh | 37 +- scripts/ci-prepare-native-runtime.sh | 2 + scripts/package-native-runtime.sh | 118 +++++- scripts/package-native-sdk-crate.sh | 314 -------------- scripts/package-native-sdk.sh | 384 ------------------ scripts/verify-native-runtime-package.sh | 27 ++ scripts/verify-native-sdk-package.sh | 237 ----------- .../kotlin/ai/meshllm/example/ExampleMain.kt | 2 +- sdk/node/README.md | 8 +- tools/xtask/src/main.rs | 1 - 20 files changed, 247 insertions(+), 1054 deletions(-) delete mode 100755 scripts/package-native-sdk-crate.sh delete mode 100755 scripts/package-native-sdk.sh delete mode 100755 scripts/verify-native-sdk-package.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76956439d3..d9ec21b4bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -189,72 +189,6 @@ jobs: secrets: HF_TOKEN: ${{ secrets.HF_TOKEN }} - build_native_sdk_runtime: - name: Build native SDK runtime ${{ matrix.name }} - needs: metadata - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - name: macOS aarch64 Metal - os: macos-15 - backend: metal - target: aarch64-apple-darwin - artifact_suffix: darwin-aarch64-metal - - name: Linux x86_64 CPU - os: ubuntu-24.04 - backend: cpu - target: x86_64-unknown-linux-gnu - artifact_suffix: linux-x86_64-cpu - env: - LLAMA_STAGE_BACKEND: ${{ matrix.backend }} - MESH_NATIVE_SDK_TARGET: ${{ matrix.target }} - steps: - - uses: actions/checkout@v5 - - - uses: dtolnay/rust-toolchain@stable - - - uses: mozilla-actions/sccache-action@v0.0.9 - - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y build-essential cmake ninja-build pkg-config libssl-dev libdbus-1-dev curl lld - - - name: Install macOS dependencies - if: runner.os == 'macOS' - run: brew install cmake ninja lld - - - name: Prepare dispatched release version - if: github.event_name == 'workflow_dispatch' - env: - RELEASE_TAG: ${{ needs.metadata.outputs.tag }} - run: scripts/release-version.sh "$RELEASE_TAG" - - - name: Package native SDK runtime - run: | - scripts/package-native-sdk.sh \ - --build \ - --backend "${{ matrix.backend }}" \ - --target "${{ matrix.target }}" \ - --out dist/native-sdk - - - name: Verify native SDK runtime artifact - run: scripts/verify-native-sdk-package.sh dist/native-sdk/*.tar.gz - - - name: Package native SDK runtime crate - run: scripts/package-native-sdk-crate.sh --out dist/native-sdk-crates dist/native-sdk/*.tar.gz - - - name: Upload native SDK runtime - uses: actions/upload-artifact@v6 - with: - name: release-native-sdk-${{ matrix.artifact_suffix }} - path: | - dist/native-sdk/*.tar.gz - dist/native-sdk/*.sha256 - dist/native-sdk-crates/*/target/package/*.crate - if-no-files-found: error - build_swift_sdk_artifact: name: Build Swift SDK XCFramework needs: metadata @@ -840,7 +774,6 @@ jobs: - metadata - build - inference_smoke_tests - - build_native_sdk_runtime - build_swift_sdk_artifact - build_linux_arm64 - build_linux_aarch64_cuda @@ -849,7 +782,7 @@ jobs: - build_linux_vulkan - build_windows_cpu - build_windows_gpu - if: ${{ always() && needs.metadata.result == 'success' && needs.metadata.outputs.canary != 'true' && needs.build.result == 'success' && needs.inference_smoke_tests.result == 'success' && needs.build_native_sdk_runtime.result == 'success' && needs.build_swift_sdk_artifact.result == 'success' && needs.build_linux_arm64.result == 'success' && (needs.build_linux_aarch64_cuda.result == 'success' || needs.build_linux_aarch64_cuda.result == 'skipped') && (needs.build_linux_cuda.result == 'success' || needs.build_linux_cuda.result == 'skipped') && (needs.build_linux_rocm.result == 'success' || needs.build_linux_rocm.result == 'skipped') && (needs.build_linux_vulkan.result == 'success' || needs.build_linux_vulkan.result == 'skipped') && needs.build_windows_cpu.result == 'success' && (needs.build_windows_gpu.result == 'success' || needs.build_windows_gpu.result == 'skipped') }} + if: ${{ always() && needs.metadata.result == 'success' && needs.metadata.outputs.canary != 'true' && needs.build.result == 'success' && needs.inference_smoke_tests.result == 'success' && needs.build_swift_sdk_artifact.result == 'success' && needs.build_linux_arm64.result == 'success' && (needs.build_linux_aarch64_cuda.result == 'success' || needs.build_linux_aarch64_cuda.result == 'skipped') && (needs.build_linux_cuda.result == 'success' || needs.build_linux_cuda.result == 'skipped') && (needs.build_linux_rocm.result == 'success' || needs.build_linux_rocm.result == 'skipped') && (needs.build_linux_vulkan.result == 'success' || needs.build_linux_vulkan.result == 'skipped') && needs.build_windows_cpu.result == 'success' && (needs.build_windows_gpu.result == 'success' || needs.build_windows_gpu.result == 'skipped') }} runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 diff --git a/crates/mesh-llm-host-runtime/src/sdk/native_runtime.rs b/crates/mesh-llm-host-runtime/src/sdk/native_runtime.rs index ade148b7c3..1057cec5d5 100644 --- a/crates/mesh-llm-host-runtime/src/sdk/native_runtime.rs +++ b/crates/mesh-llm-host-runtime/src/sdk/native_runtime.rs @@ -12,6 +12,6 @@ pub use mesh_llm_native_runtime::{ InstalledNativeRuntime, NATIVE_RUNTIME_MANIFEST_FILE, NativeRuntimeArtifact, NativeRuntimeCache, NativeRuntimeCacheRoot, NativeRuntimeFlavor, NativeRuntimeFlavorParseError, NativeRuntimeLoadPlan, NativeRuntimeManifest, NativeRuntimePruneMode, - NativeRuntimeReleaseManifest, NativeRuntimeResolution, NativeRuntimeResolver, + NativeRuntimeReleaseManifest, NativeRuntimeResolution, NativeRuntimeResolver, NativeRuntimeSdk, NativeRuntimeSource, RuntimeSelection, native_runtime_cache_root, select_native_runtime, }; diff --git a/crates/mesh-llm-native-runtime/README.md b/crates/mesh-llm-native-runtime/README.md index b126fdf303..7a1c045f00 100644 --- a/crates/mesh-llm-native-runtime/README.md +++ b/crates/mesh-llm-native-runtime/README.md @@ -48,7 +48,13 @@ Each packaged runtime directory contains `manifest.json`: } }, "rank": 0, - "libraries": ["lib/libllama.so"] + "libraries": ["lib/libllama.so"], + "sdk": { + "library": "lib/libmeshllm_ffi.so", + "library_paths": ["lib/libmeshllm_ffi.so"], + "uniffi_library": "lib/libuniffi_mesh_ffi.so", + "library_sha256": "2f1c..." + } } } ``` @@ -79,6 +85,7 @@ Important fields: - `backend`: structured backend requirements. - `rank`: optional rank adjustment. Higher compatible ranks win. - `libraries`: runtime-relative load-order library paths. +- `sdk`: optional SDK FFI library metadata packaged in the same artifact. - `url` and `sha256`: populated in release manifests for downloads. ## Release Manifest diff --git a/crates/mesh-llm-native-runtime/src/cache.rs b/crates/mesh-llm-native-runtime/src/cache.rs index 413e08591d..2ca033dbad 100644 --- a/crates/mesh-llm-native-runtime/src/cache.rs +++ b/crates/mesh-llm-native-runtime/src/cache.rs @@ -241,6 +241,7 @@ mod tests { backend: NativeRuntimeBackend::cpu(), rank: 0, libraries: vec!["lib/libmeshllm_ffi.so".to_string()], + sdk: None, url: None, sha256: None, signature: None, @@ -253,13 +254,17 @@ mod tests { fn installs_bundle_runtime_into_versioned_cache() { let temp = tempfile::tempdir().unwrap(); let source = temp.path().join("source"); - write_runtime(&source, "0.68.0", "meshllm-native-linux-x86_64-cpu"); + write_runtime(&source, "0.68.0", "meshllm-native-runtime-linux-x86_64-cpu"); let cache = NativeRuntimeCache::new(temp.path().join("cache")); let installed = cache.install_from_dir(&source).unwrap(); assert_eq!(installed.mesh_version, "0.68.0"); - assert!(installed.path.ends_with("meshllm-native-linux-x86_64-cpu")); + assert!( + installed + .path + .ends_with("meshllm-native-runtime-linux-x86_64-cpu") + ); } #[test] @@ -268,9 +273,9 @@ mod tests { let cache = NativeRuntimeCache::new(temp.path().join("cache")); for version in ["0.67.0", "0.68.0", "0.69.0"] { write_runtime( - &cache.runtime_dir(version, "meshllm-native-linux-x86_64-cpu"), + &cache.runtime_dir(version, "meshllm-native-runtime-linux-x86_64-cpu"), version, - "meshllm-native-linux-x86_64-cpu", + "meshllm-native-runtime-linux-x86_64-cpu", ); } @@ -285,18 +290,21 @@ mod tests { fn installed_runtime_exposes_load_plan() { let temp = tempfile::tempdir().unwrap(); let source = temp.path().join("source"); - write_runtime(&source, "0.68.0", "meshllm-native-linux-x86_64-cpu"); + write_runtime(&source, "0.68.0", "meshllm-native-runtime-linux-x86_64-cpu"); let cache = NativeRuntimeCache::new(temp.path().join("cache")); let installed = cache.install_from_dir(&source).unwrap(); let plan = installed.load_plan().unwrap(); - assert_eq!(plan.native_runtime_id, "meshllm-native-linux-x86_64-cpu"); + assert_eq!( + plan.native_runtime_id, + "meshllm-native-runtime-linux-x86_64-cpu" + ); assert_eq!( plan.libraries, vec![ cache - .runtime_dir("0.68.0", "meshllm-native-linux-x86_64-cpu") + .runtime_dir("0.68.0", "meshllm-native-runtime-linux-x86_64-cpu") .join("lib/libmeshllm_ffi.so") ] ); diff --git a/crates/mesh-llm-native-runtime/src/lib.rs b/crates/mesh-llm-native-runtime/src/lib.rs index 7902c33248..cf2c6f3c28 100644 --- a/crates/mesh-llm-native-runtime/src/lib.rs +++ b/crates/mesh-llm-native-runtime/src/lib.rs @@ -21,7 +21,7 @@ pub use host::{ pub use load_plan::NativeRuntimeLoadPlan; pub use manifest::{ NATIVE_RUNTIME_MANIFEST_FILE, NativeRuntimeArtifact, NativeRuntimeManifest, - NativeRuntimePlatform, NativeRuntimeReleaseManifest, + NativeRuntimePlatform, NativeRuntimeReleaseManifest, NativeRuntimeSdk, }; pub use resolver::{ CandidateEvaluation, CandidateRejection, NativeRuntimeResolution, NativeRuntimeResolver, diff --git a/crates/mesh-llm-native-runtime/src/manifest.rs b/crates/mesh-llm-native-runtime/src/manifest.rs index 8520f57454..a369945119 100644 --- a/crates/mesh-llm-native-runtime/src/manifest.rs +++ b/crates/mesh-llm-native-runtime/src/manifest.rs @@ -13,6 +13,21 @@ pub struct NativeRuntimePlatform { pub target: Option, } +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct NativeRuntimeSdk { + pub library: String, + #[serde(default)] + pub library_paths: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uniffi_library: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub library_sha256: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cargo_profile: Option, + #[serde(default)] + pub features: Vec, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct NativeRuntimeArtifact { pub id: String, @@ -25,6 +40,8 @@ pub struct NativeRuntimeArtifact { pub rank: i64, pub libraries: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] + pub sdk: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub url: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub sha256: Option, @@ -139,6 +156,20 @@ fn validate_artifact(artifact: &NativeRuntimeArtifact) -> Result<()> { artifact.id ); } + if let Some(sdk) = &artifact.sdk { + if sdk.library.trim().is_empty() { + bail!( + "native runtime artifact {} sdk library is empty", + artifact.id + ); + } + if sdk.library_paths.is_empty() { + bail!( + "native runtime artifact {} sdk library_paths is empty", + artifact.id + ); + } + } Ok(()) } @@ -170,7 +201,15 @@ mod tests { } }, "rank": 650, - "libraries": ["lib/libllama.so"] + "libraries": ["lib/libllama.so"], + "sdk": { + "library": "lib/libmeshllm_ffi.so", + "library_paths": ["lib/libmeshllm_ffi.so"], + "uniffi_library": "lib/libuniffi_mesh_ffi.so", + "library_sha256": "abc123", + "cargo_profile": "release", + "features": ["local-serving"] + } } }"#, ) @@ -181,6 +220,10 @@ mod tests { assert_eq!(manifest.runtime.id, "meshllm-runtime-linux-x86_64-cuda12"); assert_eq!(manifest.runtime.skippy_abi, "0.1.25"); assert_eq!(manifest.runtime.backend.kind.as_str(), "cuda"); + assert_eq!( + manifest.runtime.sdk.as_ref().unwrap().library, + "lib/libmeshllm_ffi.so" + ); } #[test] @@ -197,7 +240,12 @@ mod tests { "platform": { "os": "linux", "arch": "x86_64" }, "backend": { "kind": "cpu" }, "rank": 100, - "libraries": ["lib/libllama.so"] + "libraries": ["lib/libllama.so"], + "sdk": { + "library": "lib/libmeshllm_ffi.so", + "library_paths": ["lib/libmeshllm_ffi.so"], + "uniffi_library": "lib/libuniffi_mesh_ffi.so" + } } ] }"#, @@ -206,5 +254,6 @@ mod tests { assert_eq!(manifest.artifacts.len(), 1); assert_eq!(manifest.artifacts[0].backend, NativeRuntimeBackend::cpu()); + assert!(manifest.artifacts[0].sdk.is_some()); } } diff --git a/crates/mesh-llm-native-runtime/src/resolver.rs b/crates/mesh-llm-native-runtime/src/resolver.rs index 87d83d90f4..e51c2ca0ac 100644 --- a/crates/mesh-llm-native-runtime/src/resolver.rs +++ b/crates/mesh-llm-native-runtime/src/resolver.rs @@ -429,6 +429,7 @@ mod tests { backend, rank: 0, libraries: vec!["lib/libllama.so".to_string()], + sdk: None, url: None, sha256: None, signature: None, diff --git a/crates/mesh-llm-runtime-install/src/lib.rs b/crates/mesh-llm-runtime-install/src/lib.rs index 08615989a3..1ffbb4955f 100644 --- a/crates/mesh-llm-runtime-install/src/lib.rs +++ b/crates/mesh-llm-runtime-install/src/lib.rs @@ -504,6 +504,7 @@ mod tests { backend: NativeRuntimeBackend::cpu(), rank: 0, libraries: vec!["lib/libllama.so".to_string()], + sdk: None, url: Some("https://example.invalid/runtime.tar.gz".to_string()), sha256: Some("a".repeat(64)), signature: signature.map(str::to_string), diff --git a/docs/SDK.md b/docs/SDK.md index 69cd3cb9a2..5bbfd9354c 100644 --- a/docs/SDK.md +++ b/docs/SDK.md @@ -229,13 +229,15 @@ meshllm-native-runtime--/ lib/ libllama.{dylib|so|dll} libggml*.{dylib|so|dll} + libmeshllm_ffi.{dylib|so} or meshllm_ffi.dll + libuniffi_mesh_ffi.{dylib|so} or uniffi_mesh_ffi.dll ``` The manifest records the MeshLLM version, exact Skippy ABI, platform, -structured backend requirements, load-order library paths, release URL, -checksum, and optional signature metadata. Runtime compatibility is exact -Skippy ABI plus platform/backend requirements; MeshLLM version remains part of -cache layout and pruning. +structured backend requirements, load-order runtime library paths, SDK FFI +library paths, release URL, checksum, and optional signature metadata. Runtime +compatibility is exact Skippy ABI plus platform/backend requirements; MeshLLM +version remains part of cache layout and pruning. Baseline artifact names: diff --git a/docs/design/NATIVE_RUNTIMES.md b/docs/design/NATIVE_RUNTIMES.md index 1431dc3644..0797935b65 100644 --- a/docs/design/NATIVE_RUNTIMES.md +++ b/docs/design/NATIVE_RUNTIMES.md @@ -46,6 +46,8 @@ The artifact manifest should include at least: - `platform` - `backend` - `libraries` +- `sdk`, with the SDK FFI library and UniFFI alias when the artifact is used by + SDK consumers - checksums - signature or attestation metadata - release URL diff --git a/scripts/ci-kotlin-sdk-smoke.sh b/scripts/ci-kotlin-sdk-smoke.sh index 0ffbb7a499..b5cebb78d2 100755 --- a/scripts/ci-kotlin-sdk-smoke.sh +++ b/scripts/ci-kotlin-sdk-smoke.sh @@ -33,42 +33,23 @@ scripts/check-sdk-contract.sh scripts/package-sdk-console-assets.sh --sdk kotlin scripts/verify-sdk-console-assets.sh --sdk kotlin -scripts/prepare-llama.sh "${MESH_LLM_LLAMA_PIN_SHA:-pinned}" -LLAMA_STAGE_BACKEND=cpu \ -LLAMA_STAGE_BUILD_DIR="$REPO_ROOT/.deps/llama-build/build-stage-abi-ci-kotlin-cpu" \ -LLAMA_BUILD_DIR="$REPO_ROOT/.deps/llama-build/build-stage-abi-ci-kotlin-cpu" \ - scripts/build-llama.sh - -LLAMA_STAGE_BACKEND=cpu \ -LLAMA_STAGE_BUILD_DIR="$REPO_ROOT/.deps/llama-build/build-stage-abi-ci-kotlin-cpu" \ - retry_transient cargo build -p mesh-llm-ffi --no-default-features --features host,embedded-runtime - -native_sdk_out="$REPO_ROOT/target/kotlin-native-sdk" -LLAMA_STAGE_BACKEND=cpu \ -LLAMA_STAGE_BUILD_DIR="$REPO_ROOT/.deps/llama-build/build-stage-abi-ci-kotlin-cpu" \ - retry_transient scripts/package-native-sdk.sh \ - --backend cpu \ - --profile debug \ - --out "$native_sdk_out" -scripts/verify-native-sdk-package.sh "$native_sdk_out"/meshllm-native-*.tar.gz -native_sdk_artifact_dir="$(find "$native_sdk_out" -mindepth 1 -maxdepth 1 -type d -name 'meshllm-native-*' -print -quit)" -if [[ -z "$native_sdk_artifact_dir" ]]; then - echo "native SDK artifact directory not found under $native_sdk_out" >&2 - exit 1 -fi -native_sdk_uniffi_library="$( - python3 - "$native_sdk_artifact_dir/manifest.json" <<'PY' +native_runtime_dir="$( + MESH_NATIVE_RUNTIME_PROFILE=debug \ + retry_transient scripts/ci-prepare-native-runtime.sh "$REPO_ROOT/target/kotlin-native-runtime" cpu +)" +native_runtime_uniffi_library="$( + python3 - "$native_runtime_dir/manifest.json" <<'PY' import json import os import sys with open(sys.argv[1], encoding="utf-8") as fh: manifest = json.load(fh) -print(os.path.dirname(manifest.get("uniffi_library") or manifest["library"])) +sdk = manifest["runtime"]["sdk"] +print(os.path.dirname(sdk.get("uniffi_library") or sdk["library"])) PY )" -export MESHLLM_KOTLIN_JNA_LIBRARY_PATH="$native_sdk_artifact_dir/$native_sdk_uniffi_library" -native_runtime_dir="$(scripts/ci-prepare-native-runtime.sh "$REPO_ROOT/target/kotlin-native-runtime" cpu)" +export MESHLLM_KOTLIN_JNA_LIBRARY_PATH="$native_runtime_dir/$native_runtime_uniffi_library" export MESHLLM_NATIVE_RUNTIME_ARTIFACT_DIR="$native_runtime_dir" scripts/ci-sdk-fixture.sh "$1" "$2" "$3" -- \ diff --git a/scripts/ci-prepare-native-runtime.sh b/scripts/ci-prepare-native-runtime.sh index 8354f6c7c1..ffd528b8b6 100755 --- a/scripts/ci-prepare-native-runtime.sh +++ b/scripts/ci-prepare-native-runtime.sh @@ -10,6 +10,7 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" OUT_DIR="$1" BACKEND="${2:-cpu}" BUILD_DIR="$REPO_ROOT/.deps/llama-build/build-stage-abi-ci-runtime-${BACKEND}" +PROFILE="${MESH_NATIVE_RUNTIME_PROFILE:-release}" cd "$REPO_ROOT" @@ -21,6 +22,7 @@ LLAMA_BUILD_DIR="$BUILD_DIR" \ scripts/package-native-runtime.sh \ --build \ --backend "$BACKEND" \ + --profile "$PROFILE" \ --out "$OUT_DIR" >&2 scripts/verify-native-runtime-package.sh "$OUT_DIR"/meshllm-native-runtime-*.tar.gz >&2 diff --git a/scripts/package-native-runtime.sh b/scripts/package-native-runtime.sh index 3786ae9124..87241fb4c2 100755 --- a/scripts/package-native-runtime.sh +++ b/scripts/package-native-runtime.sh @@ -8,6 +8,7 @@ BUILD=0 OUT_DIR="$REPO_ROOT/dist/native-runtimes" BACKEND="${LLAMA_STAGE_BACKEND:-${SKIPPY_LLAMA_BACKEND:-cpu}}" TARGET_TRIPLE="${MESH_NATIVE_RUNTIME_TARGET:-}" +PROFILE="${MESH_NATIVE_RUNTIME_PROFILE:-release}" LLAMA_WORKDIR="${LLAMA_WORKDIR:-$REPO_ROOT/.deps/llama.cpp}" LLAMA_BUILD_ROOT="${MESH_LLM_LLAMA_BUILD_ROOT:-$REPO_ROOT/.deps/llama-build}" @@ -16,12 +17,14 @@ usage() { Usage: scripts/package-native-runtime.sh [options] Package a MeshLLM native runtime artifact containing the patched llama/Skippy -shared libraries selected by `mesh-llm runtime install`. +shared libraries selected by `mesh-llm runtime install` and the MeshLLM FFI +library consumed by SDKs. Options: --build Build patched llama.cpp shared libraries before packaging. --backend NAME cpu, metal, cuda, rocm, hip, vulkan, or cuda-blackwell. --target TRIPLE Runtime target triple. Defaults to the host target. + --profile PROFILE Cargo profile to package: release or debug. Defaults to release. --out DIR Output directory. Defaults to dist/native-runtimes. -h, --help Show this help. @@ -30,6 +33,7 @@ Environment: LLAMA_STAGE_AMDGPU_TARGETS / SKIPPY_AMDGPU_TARGETS LLAMA_STAGE_BUILD_DIR MESH_NATIVE_RUNTIME_TARGET + MESH_NATIVE_RUNTIME_PROFILE MESH_LLM_LLAMA_PIN_SHA EOF } @@ -48,6 +52,10 @@ while [[ "$#" -gt 0 ]]; do TARGET_TRIPLE="${2:?missing target triple}" shift 2 ;; + --profile) + PROFILE="${2:?missing cargo profile}" + shift 2 + ;; --out) OUT_DIR="${2:?missing output directory}" shift 2 @@ -72,6 +80,14 @@ case "$BACKEND" in ;; esac +case "$PROFILE" in + release|debug) ;; + *) + echo "unsupported native runtime cargo profile: $PROFILE" >&2 + exit 1 + ;; +esac + host_os() { case "$(uname -s)" in Darwin) printf 'darwin\n' ;; @@ -213,6 +229,54 @@ primary_library_name() { esac } +library_extension() { + case "$1" in + *apple-darwin) printf 'dylib\n' ;; + *linux*) printf 'so\n' ;; + *windows*) printf 'dll\n' ;; + *) echo "cannot infer dynamic library extension for target: $1" >&2; exit 1 ;; + esac +} + +ffi_library_basename() { + case "$1" in + dll) printf 'meshllm_ffi.dll\n' ;; + *) printf 'libmeshllm_ffi.%s\n' "$1" ;; + esac +} + +uniffi_library_basename() { + case "$1" in + dll) printf 'uniffi_mesh_ffi.dll\n' ;; + *) printf 'libuniffi_mesh_ffi.%s\n' "$1" ;; + esac +} + +target_cargo_dir() { + if [[ "$TARGET_TRIPLE" != "$(default_target_triple)" ]]; then + printf '%s\n' "$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE" + else + printf '%s\n' "$REPO_ROOT/target/$PROFILE" + fi +} + +find_ffi_library() { + local name="$1" + local cargo_dir="$2" + local path + for path in \ + "$cargo_dir/$name" \ + "$cargo_dir/deps/$name" \ + "$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE/$name" \ + "$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE/deps/$name"; do + if [[ -f "$path" ]]; then + printf '%s\n' "$path" + return 0 + fi + done + return 1 +} + collect_runtime_libraries() { local pattern primary pattern="$(library_pattern)" @@ -256,6 +320,18 @@ if [[ "$BUILD" == "1" ]]; then LLAMA_BUILD_DIR="$LLAMA_STAGE_BUILD_DIR" \ LLAMA_STAGE_BUILD_DIR="$LLAMA_STAGE_BUILD_DIR" \ "$SCRIPT_DIR/build-llama.sh" + + cargo_args=(build -p mesh-llm-ffi --no-default-features --features host,embedded-runtime) + if [[ "$PROFILE" == "release" ]]; then + cargo_args+=(--release) + fi + if [[ "$TARGET_TRIPLE" != "$(default_target_triple)" ]]; then + cargo_args+=(--target "$TARGET_TRIPLE") + fi + LLAMA_STAGE_LINK_MODE=dynamic \ + LLAMA_STAGE_BACKEND="$(build_backend)" \ + LLAMA_STAGE_BUILD_DIR="$LLAMA_STAGE_BUILD_DIR" \ + cargo "${cargo_args[@]}" fi platform="$(target_platform "$TARGET_TRIPLE")" @@ -264,6 +340,17 @@ runtime_arch="$(target_runtime_arch "$TARGET_TRIPLE")" flavor="$(backend_flavor)" artifact_id="meshllm-native-runtime-${platform}-${flavor}" stage_dir="$OUT_DIR/$artifact_id" +lib_ext="$(library_extension "$TARGET_TRIPLE")" +ffi_lib_name="$(ffi_library_basename "$lib_ext")" +uniffi_lib_name="$(uniffi_library_basename "$lib_ext")" +cargo_dir="$(target_cargo_dir)" +ffi_lib_path="$(find_ffi_library "$ffi_lib_name" "$cargo_dir" || true)" +if [[ -z "$ffi_lib_path" ]]; then + echo "native SDK FFI library not found: $ffi_lib_name" >&2 + echo "looked in: $cargo_dir and $cargo_dir/deps" >&2 + echo "rerun with --build or build mesh-llm-ffi first" >&2 + exit 1 +fi runtime_libraries=() while IFS= read -r library; do @@ -291,9 +378,14 @@ for library in "${runtime_libraries[@]}"; do cp "$library" "$stage_dir/lib/$name" library_paths+=("lib/$name") done +cp "$ffi_lib_path" "$stage_dir/lib/$ffi_lib_name" +cp "$ffi_lib_path" "$stage_dir/lib/$uniffi_lib_name" primary_library="lib/$primary_name" primary_sha="$(sha256_file "$stage_dir/$primary_library")" +ffi_library="lib/$ffi_lib_name" +uniffi_library="lib/$uniffi_lib_name" +ffi_sha="$(sha256_file "$stage_dir/$ffi_library")" mesh_version="$(workspace_version)" abi_version="$(skippy_abi_version)" @@ -373,6 +465,20 @@ manifest = { "backend": backend_manifest, "rank": int(os.environ.get("MESH_LLM_NATIVE_RUNTIME_RANK") or 0), "libraries": library_paths, + "sdk": { + "library": "$ffi_library", + "library_paths": ["$ffi_library"], + "uniffi_library": "$uniffi_library", + "library_sha256": "$ffi_sha", + "cargo_profile": "$PROFILE", + "features": [ + "mesh-inference", + "model-management", + "local-serving", + "chat", + "responses", + ], + }, "url": None, "sha256": None, "signature": None, @@ -382,6 +488,8 @@ manifest = { "backend": "$BACKEND", "primary_library": primary_library, "library_sha256": "$primary_sha", + "ffi_library": "$ffi_library", + "ffi_library_sha256": "$ffi_sha", "llama_upstream_sha": "$upstream_sha" or None, "llama_patched_sha": "$patched_sha" or None, "llama_patch_digest": "$patch_digest" or None, @@ -396,7 +504,8 @@ PY cat > "$stage_dir/README.md" < "$archive.sha256" echo "packaged native runtime:" echo " artifact: $artifact_id" echo " primary: $stage_dir/$primary_library" +echo " ffi: $stage_dir/$ffi_library" echo " archive: $archive" diff --git a/scripts/package-native-sdk-crate.sh b/scripts/package-native-sdk-crate.sh deleted file mode 100755 index 426c4125bc..0000000000 --- a/scripts/package-native-sdk-crate.sh +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -OUT_DIR="$REPO_ROOT/dist/native-sdk-crates" -TMP_ROOT="" -trap 'rm -rf "$TMP_ROOT"' EXIT - -usage() { - cat >&2 <<'EOF' -Usage: scripts/package-native-sdk-crate.sh [options] - -Generate a crates.io-ready native runtime crate from a verified native SDK -runtime artifact. The generated crate contains the native library files and -exports their paths through Cargo build metadata. - -Options: - --out DIR Output directory. Defaults to dist/native-sdk-crates. - -h, --help Show this help. - -Generated crates use: - links = "meshllm_native_runtime" - -Cargo exposes build metadata to dependents as: - DEP_MESHLLM_NATIVE_RUNTIME_ARTIFACT_ID - DEP_MESHLLM_NATIVE_RUNTIME_ARTIFACT_DIR - DEP_MESHLLM_NATIVE_RUNTIME_MANIFEST - DEP_MESHLLM_NATIVE_RUNTIME_LIB_DIR - DEP_MESHLLM_NATIVE_RUNTIME_LIBRARY -EOF -} - -while [[ "$#" -gt 0 ]]; do - case "$1" in - --out) - OUT_DIR="${2:?missing output directory}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - --) - shift - break - ;; - -*) - echo "unknown argument: $1" >&2 - usage - exit 1 - ;; - *) - break - ;; - esac -done - -if [[ "$#" -ne 1 ]]; then - usage - exit 1 -fi - -INPUT="$1" - -"$SCRIPT_DIR/verify-native-sdk-package.sh" "$INPUT" - -artifact_dir_for_input() { - local input="$1" - - if [[ -d "$input" ]]; then - printf '%s\n' "$input" - return 0 - fi - - TMP_ROOT="$(mktemp -d)" - tar -C "$TMP_ROOT" -xzf "$input" - find "$TMP_ROOT" -mindepth 1 -maxdepth 1 -type d -print -quit -} - -artifact_dir="$(artifact_dir_for_input "$INPUT")" -manifest="$artifact_dir/manifest.json" - -read_manifest_field() { - python3 - "$manifest" "$1" <<'PY' -import json -import sys - -with open(sys.argv[1], encoding="utf-8") as fh: - manifest = json.load(fh) -value = manifest[sys.argv[2]] -if value is None: - value = "" -print(value) -PY -} - -artifact_id="$(read_manifest_field artifact_id)" -version="$(read_manifest_field sdk_version)" -platform="$(read_manifest_field platform)" -flavor="$(read_manifest_field flavor)" -target_triple="$(read_manifest_field target_triple)" -backend="$(read_manifest_field backend)" -crate_name="${artifact_id//_/-}" -lib_name="${crate_name//-/_}" -crate_dir="$OUT_DIR/$crate_name" - -rm -rf "$crate_dir" -mkdir -p "$crate_dir/native" "$crate_dir/src" - -cp "$manifest" "$crate_dir/native/manifest.json" -cp -R "$artifact_dir/lib" "$crate_dir/native/lib" - -cat > "$crate_dir/Cargo.toml" < "$crate_dir/build.rs" <<'EOF' -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; - -fn main() { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); - let source_artifact_dir = manifest_dir.join("native"); - let source_manifest_path = source_artifact_dir.join("manifest.json"); - - let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); - let artifact_dir = out_dir.join("native"); - if artifact_dir.exists() { - fs::remove_dir_all(&artifact_dir).expect("remove stale native runtime from OUT_DIR"); - } - copy_dir_all(&source_artifact_dir, &artifact_dir) - .expect("copy native runtime artifact into OUT_DIR"); - - let manifest_path = artifact_dir.join("manifest.json"); - let lib_dir = artifact_dir.join("lib"); - - let manifest = fs::read_to_string(&manifest_path).expect("read native runtime manifest"); - let artifact_id = json_string_field(&manifest, "artifact_id").expect("manifest artifact_id"); - let library = json_string_field(&manifest, "library").expect("manifest library"); - - let library_path = artifact_dir.join(&library); - - println!("cargo:rerun-if-changed={}", source_manifest_path.display()); - println!("cargo:rerun-if-changed={}", source_artifact_dir.join(&library).display()); - - println!("cargo:artifact_id={artifact_id}"); - println!("cargo:artifact_dir={}", artifact_dir.display()); - println!("cargo:manifest={}", manifest_path.display()); - println!("cargo:lib_dir={}", lib_dir.display()); - println!("cargo:library={}", library_path.display()); - - println!("cargo:rustc-env=MESHLLM_NATIVE_RUNTIME_ARTIFACT_ID={artifact_id}"); - println!( - "cargo:rustc-env=MESHLLM_NATIVE_RUNTIME_ARTIFACT_DIR={}", - artifact_dir.display() - ); - println!( - "cargo:rustc-env=MESHLLM_NATIVE_RUNTIME_MANIFEST={}", - manifest_path.display() - ); - println!( - "cargo:rustc-env=MESHLLM_NATIVE_RUNTIME_LIB_DIR={}", - lib_dir.display() - ); - println!( - "cargo:rustc-env=MESHLLM_NATIVE_RUNTIME_LIBRARY={}", - library_path.display() - ); -} - -fn copy_dir_all(source: &Path, destination: &Path) -> std::io::Result<()> { - fs::create_dir_all(destination)?; - for entry in fs::read_dir(source)? { - let entry = entry?; - let file_type = entry.file_type()?; - let destination_path = destination.join(entry.file_name()); - if file_type.is_dir() { - copy_dir_all(&entry.path(), &destination_path)?; - } else { - fs::copy(entry.path(), destination_path)?; - } - } - Ok(()) -} - -fn json_string_field(source: &str, key: &str) -> Option { - let needle = format!("\"{key}\""); - let key_index = source.find(&needle)?; - let after_key = &source[key_index + needle.len()..]; - let colon_index = after_key.find(':')?; - let mut rest = after_key[colon_index + 1..].trim_start(); - if !rest.starts_with('"') { - return None; - } - rest = &rest[1..]; - - let mut value = String::new(); - let mut escaped = false; - for ch in rest.chars() { - if escaped { - value.push(ch); - escaped = false; - continue; - } - match ch { - '\\' => escaped = true, - '"' => return Some(value), - _ => value.push(ch), - } - } - None -} -EOF - -cat > "$crate_dir/src/lib.rs" <<'EOF' -use std::path::PathBuf; - -pub const MANIFEST_JSON: &str = include_str!("../native/manifest.json"); -pub const ARTIFACT_ID: &str = env!("MESHLLM_NATIVE_RUNTIME_ARTIFACT_ID"); - -pub fn artifact_dir() -> PathBuf { - PathBuf::from(env!("MESHLLM_NATIVE_RUNTIME_ARTIFACT_DIR")) -} - -pub fn manifest_path() -> PathBuf { - PathBuf::from(env!("MESHLLM_NATIVE_RUNTIME_MANIFEST")) -} - -pub fn lib_dir() -> PathBuf { - PathBuf::from(env!("MESHLLM_NATIVE_RUNTIME_LIB_DIR")) -} - -pub fn library_path() -> PathBuf { - PathBuf::from(env!("MESHLLM_NATIVE_RUNTIME_LIBRARY")) -} - -EOF - -cat > "$crate_dir/README.md" <&2 <<'EOF' -Usage: scripts/package-native-sdk.sh [options] - -Package a backend-flavoured MeshLLM native SDK runtime artifact. - -Options: - --build Build patched llama.cpp and mesh-llm-ffi before packaging. - --backend NAME cpu, metal, cuda, rocm, hip, or vulkan. - --target TRIPLE Rust target triple. Defaults to the host target. - --profile PROFILE Cargo profile to package: release or debug. Defaults to release. - --out DIR Output directory. Defaults to dist/native-sdk. - -h, --help Show this help. - -Environment: - LLAMA_STAGE_CUDA_ARCHITECTURES / SKIPPY_CUDA_ARCHITECTURES - LLAMA_STAGE_AMDGPU_TARGETS / SKIPPY_AMDGPU_TARGETS - LLAMA_STAGE_BUILD_DIR - MESH_NATIVE_SDK_TARGET - MESH_NATIVE_SDK_PROFILE - MESH_LLM_LLAMA_PIN_SHA -EOF -} - -while [[ "$#" -gt 0 ]]; do - case "$1" in - --build) - BUILD=1 - shift - ;; - --backend) - BACKEND="${2:?missing backend}" - shift 2 - ;; - --target) - TARGET_TRIPLE="${2:?missing target triple}" - shift 2 - ;; - --profile) - PROFILE="${2:?missing cargo profile}" - shift 2 - ;; - --out) - OUT_DIR="${2:?missing output directory}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "unknown argument: $1" >&2 - usage - exit 1 - ;; - esac -done - -case "$BACKEND" in - cpu|metal|cuda|cuda-blackwell|rocm|hip|vulkan) ;; - *) - echo "unsupported native SDK backend: $BACKEND" >&2 - exit 1 - ;; -esac - -case "$PROFILE" in - release|debug) ;; - *) - echo "unsupported native SDK cargo profile: $PROFILE" >&2 - exit 1 - ;; -esac - -host_os() { - case "$(uname -s)" in - Darwin) printf 'darwin\n' ;; - Linux) printf 'linux\n' ;; - MINGW*|MSYS*|CYGWIN*) printf 'windows\n' ;; - *) uname -s | tr '[:upper:]' '[:lower:]' ;; - esac -} - -host_arch() { - case "$(uname -m)" in - arm64|aarch64) printf 'aarch64\n' ;; - x86_64|amd64) printf 'x86_64\n' ;; - *) uname -m ;; - esac -} - -default_target_triple() { - case "$(host_os)/$(host_arch)" in - darwin/aarch64) printf 'aarch64-apple-darwin\n' ;; - darwin/x86_64) printf 'x86_64-apple-darwin\n' ;; - linux/x86_64) printf 'x86_64-unknown-linux-gnu\n' ;; - linux/aarch64) printf 'aarch64-unknown-linux-gnu\n' ;; - windows/x86_64) printf 'x86_64-pc-windows-msvc\n' ;; - *) printf '\n' ;; - esac -} - -target_platform() { - case "$1" in - aarch64-apple-darwin) printf 'darwin-aarch64\n' ;; - x86_64-apple-darwin) printf 'darwin-x86_64\n' ;; - x86_64-unknown-linux-gnu) printf 'linux-x86_64\n' ;; - aarch64-unknown-linux-gnu) printf 'linux-aarch64\n' ;; - aarch64-linux-android) printf 'android-arm64-v8a\n' ;; - armv7-linux-androideabi) printf 'android-armeabi-v7a\n' ;; - x86_64-linux-android) printf 'android-x86_64\n' ;; - x86_64-pc-windows-msvc) printf 'windows-x86_64\n' ;; - *) printf '%s\n' "$1" | tr '_' '-' ;; - esac -} - -library_extension() { - case "$1" in - *apple-darwin) printf 'dylib\n' ;; - *linux*|*android*) printf 'so\n' ;; - *windows*) printf 'dll\n' ;; - *) echo "cannot infer dynamic library extension for target: $1" >&2; exit 1 ;; - esac -} - -library_basename() { - case "$1" in - dll) printf 'meshllm_ffi.dll\n' ;; - *) printf 'libmeshllm_ffi.%s\n' "$1" ;; - esac -} - -uniffi_library_basename() { - case "$1" in - dll) printf 'uniffi_mesh_ffi.dll\n' ;; - *) printf 'libuniffi_mesh_ffi.%s\n' "$1" ;; - esac -} - -sanitize_component() { - printf '%s' "$1" | tr ';, /:' '_____' | tr -cd 'A-Za-z0-9_.-' -} - -backend_flavor() { - case "$BACKEND" in - cuda) printf 'cuda\n' ;; - cuda-blackwell) printf 'cuda-blackwell\n' ;; - rocm|hip) printf 'rocm\n' ;; - *) - printf '%s\n' "$BACKEND" - ;; - esac -} - -build_backend() { - case "$BACKEND" in - cuda-blackwell) printf 'cuda\n' ;; - hip) printf 'rocm\n' ;; - *) printf '%s\n' "$BACKEND" ;; - esac -} - -target_runtime_os() { - case "$1" in - *apple-darwin) printf 'macos\n' ;; - *linux*|*android*) printf 'linux\n' ;; - *windows*) printf 'windows\n' ;; - *) echo "cannot infer runtime os for target: $1" >&2; exit 1 ;; - esac -} - -target_runtime_arch() { - case "$1" in - aarch64-*) printf 'aarch64\n' ;; - x86_64-*) printf 'x86_64\n' ;; - armv7-*) printf 'arm\n' ;; - *) echo "cannot infer runtime arch for target: $1" >&2; exit 1 ;; - esac -} - -sha256_file() { - if command -v shasum >/dev/null 2>&1; then - shasum -a 256 "$1" | awk '{print $1}' - elif command -v sha256sum >/dev/null 2>&1; then - sha256sum "$1" | awk '{print $1}' - else - echo "shasum or sha256sum is required" >&2 - exit 1 - fi -} - -workspace_version() { - python3 - "$REPO_ROOT/Cargo.toml" <<'PY' -import re -import sys - -in_workspace_package = False -for line in open(sys.argv[1], encoding="utf-8"): - stripped = line.strip() - if stripped == "[workspace.package]": - in_workspace_package = True - continue - if stripped.startswith("[") and stripped != "[workspace.package]": - in_workspace_package = False - if in_workspace_package: - match = re.match(r'version\s*=\s*"([^"]+)"', stripped) - if match: - print(match.group(1)) - raise SystemExit(0) -raise SystemExit("workspace package version not found") -PY -} - -if [[ -z "$TARGET_TRIPLE" ]]; then - TARGET_TRIPLE="$(default_target_triple)" -fi -if [[ -z "$TARGET_TRIPLE" ]]; then - echo "could not infer target triple; pass --target" >&2 - exit 1 -fi - -if [[ -z "${LLAMA_STAGE_BUILD_DIR:-}" ]]; then - LLAMA_STAGE_BUILD_DIR="$(LLAMA_STAGE_BACKEND="$(build_backend)" "$SCRIPT_DIR/build-llama.sh" --print-build-dir)" -fi - -if [[ "$BUILD" == "1" ]]; then - "$SCRIPT_DIR/prepare-llama.sh" "${MESH_LLM_LLAMA_PIN_SHA:-pinned}" - LLAMA_STAGE_BACKEND="$(build_backend)" \ - LLAMA_BUILD_DIR="$LLAMA_STAGE_BUILD_DIR" \ - LLAMA_STAGE_BUILD_DIR="$LLAMA_STAGE_BUILD_DIR" \ - "$SCRIPT_DIR/build-llama.sh" - - cargo_args=(build -p mesh-llm-ffi --no-default-features --features host,embedded-runtime) - if [[ "$PROFILE" == "release" ]]; then - cargo_args+=(--release) - fi - if [[ "$TARGET_TRIPLE" != "$(default_target_triple)" ]]; then - cargo_args+=(--target "$TARGET_TRIPLE") - fi - LLAMA_STAGE_BACKEND="$(build_backend)" \ - LLAMA_STAGE_BUILD_DIR="$LLAMA_STAGE_BUILD_DIR" \ - cargo "${cargo_args[@]}" -fi - -lib_ext="$(library_extension "$TARGET_TRIPLE")" -lib_name="$(library_basename "$lib_ext")" -uniffi_lib_name="$(uniffi_library_basename "$lib_ext")" -platform="$(target_platform "$TARGET_TRIPLE")" -runtime_os="$(target_runtime_os "$TARGET_TRIPLE")" -runtime_arch="$(target_runtime_arch "$TARGET_TRIPLE")" -flavor="$(backend_flavor)" -artifact_id="meshllm-native-${platform}-${flavor}" - -target_dir="$REPO_ROOT/target/$PROFILE" -if [[ "$TARGET_TRIPLE" != "$(default_target_triple)" ]]; then - target_dir="$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE" -fi - -lib_path="$target_dir/$lib_name" -if [[ ! -f "$lib_path" && -f "$target_dir/deps/$lib_name" ]]; then - lib_path="$target_dir/deps/$lib_name" -fi -if [[ ! -f "$lib_path" && -f "$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE/$lib_name" ]]; then - lib_path="$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE/$lib_name" -fi -if [[ ! -f "$lib_path" && -f "$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE/deps/$lib_name" ]]; then - lib_path="$REPO_ROOT/target/$TARGET_TRIPLE/$PROFILE/deps/$lib_name" -fi - -if [[ ! -f "$lib_path" ]]; then - echo "native SDK library not found: $lib_name" >&2 - echo "looked in: $target_dir and $target_dir/deps" >&2 - echo "rerun with --build or build mesh-llm-ffi first" >&2 - exit 1 -fi - -stage_dir="$OUT_DIR/$artifact_id" -rm -rf "$stage_dir" -mkdir -p "$stage_dir/lib" - -cp "$lib_path" "$stage_dir/lib/$lib_name" -cp "$lib_path" "$stage_dir/lib/$uniffi_lib_name" - -patched_sha="" -upstream_sha="" -patch_digest="" -if [[ -f "$LLAMA_WORKDIR/.mesh-llm-patched-sha" ]]; then - patched_sha="$(tr -d '[:space:]' < "$LLAMA_WORKDIR/.mesh-llm-patched-sha")" -fi -if [[ -f "$LLAMA_WORKDIR/.mesh-llm-upstream-sha" ]]; then - upstream_sha="$(tr -d '[:space:]' < "$LLAMA_WORKDIR/.mesh-llm-upstream-sha")" -fi -if [[ -f "$LLAMA_WORKDIR/.mesh-llm-patch-digest" ]]; then - patch_digest="$(tr -d '[:space:]' < "$LLAMA_WORKDIR/.mesh-llm-patch-digest")" -fi - -lib_sha="$(sha256_file "$stage_dir/lib/$lib_name")" -sdk_version="$(workspace_version)" - -python3 - "$stage_dir/manifest.json" < "$stage_dir/README.md" < "$archive.sha256" - -echo "packaged native SDK runtime:" -echo " artifact: $artifact_id" -echo " library: $stage_dir/lib/$lib_name" -echo " archive: $archive" diff --git a/scripts/verify-native-runtime-package.sh b/scripts/verify-native-runtime-package.sh index d1f5334e14..6ac5eaab2a 100755 --- a/scripts/verify-native-runtime-package.sh +++ b/scripts/verify-native-runtime-package.sh @@ -12,6 +12,7 @@ Verifies MeshLLM native runtime artifacts: - manifest schema and resolver fields - artifact directory name matches runtime.id - all runtime.libraries exist + - runtime.sdk libraries exist when SDK metadata is present - library_sha256 matches the primary library - archive checksum sidecar when present EOF @@ -124,6 +125,32 @@ for rel_path in runtime["libraries"]: if not os.path.isfile(path): raise SystemExit(f"missing library: {path}") +sdk = runtime.get("sdk") +if sdk is not None: + if not isinstance(sdk, dict): + raise SystemExit("runtime sdk metadata must be an object") + for key in ("library", "library_paths", "uniffi_library", "library_sha256"): + if not sdk.get(key): + raise SystemExit(f"runtime sdk metadata must declare {key}") + if not isinstance(sdk["library_paths"], list) or not sdk["library_paths"]: + raise SystemExit("runtime sdk library_paths must be a non-empty list") + sdk_paths = list(sdk["library_paths"]) + sdk_paths.append(sdk["uniffi_library"]) + for rel_path in sdk_paths: + if os.path.isabs(rel_path) or ".." in rel_path.split(os.sep): + raise SystemExit(f"sdk library path must be relative inside the artifact: {rel_path}") + path = os.path.join(artifact_dir, rel_path) + if not os.path.isfile(path): + raise SystemExit(f"missing SDK library: {path}") + sdk_library = os.path.join(artifact_dir, sdk["library"]) + with open(sdk_library, "rb") as fh: + actual = hashlib.sha256(fh.read()).hexdigest() + if actual != sdk["library_sha256"]: + raise SystemExit( + f"sdk library_sha256 mismatch for {sdk['library']}: " + f"{actual} != {sdk['library_sha256']}" + ) + build = manifest.get("build") or {} library_sha256 = build.get("library_sha256") primary_library = build.get("primary_library") or runtime["libraries"][0] diff --git a/scripts/verify-native-sdk-package.sh b/scripts/verify-native-sdk-package.sh deleted file mode 100755 index 266370e9db..0000000000 --- a/scripts/verify-native-sdk-package.sh +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -TMP_ROOT="" -trap 'rm -rf "$TMP_ROOT"' EXIT - -usage() { - cat >&2 <<'EOF' -Usage: scripts/verify-native-sdk-package.sh [...] - -Verifies MeshLLM native SDK runtime artifacts: - - archive checksum sidecar when present - - manifest schema and required fields - - artifact directory name matches manifest artifact_id - - native library exists - - library_sha256 matches the primary library - - artifact_id matches platform/flavor -EOF -} - -sha256_file() { - if command -v shasum >/dev/null 2>&1; then - shasum -a 256 "$1" | awk '{print $1}' - elif command -v sha256sum >/dev/null 2>&1; then - sha256sum "$1" | awk '{print $1}' - else - echo "shasum or sha256sum is required" >&2 - exit 1 - fi -} - -verify_sidecar_checksum() { - local archive="$1" - local sidecar="$archive.sha256" - - if [[ ! -f "$sidecar" ]]; then - return 0 - fi - - local expected actual - expected="$(awk '{print $1}' "$sidecar")" - actual="$(sha256_file "$archive")" - if [[ "$expected" != "$actual" ]]; then - echo "archive checksum mismatch: $archive" >&2 - echo " expected: $expected" >&2 - echo " actual: $actual" >&2 - exit 1 - fi -} - -artifact_dir_for_input() { - local input="$1" - - if [[ -d "$input" ]]; then - printf '%s\n' "$input" - return 0 - fi - - case "$input" in - *.tar.gz|*.tgz) ;; - *) - echo "unsupported native SDK artifact input: $input" >&2 - exit 1 - ;; - esac - - verify_sidecar_checksum "$input" - - if [[ -z "$TMP_ROOT" ]]; then - TMP_ROOT="$(mktemp -d)" - fi - - local extract_dir - extract_dir="$TMP_ROOT/$(basename "$input" | tr -cd 'A-Za-z0-9_.-')" - mkdir -p "$extract_dir" - tar -C "$extract_dir" -xzf "$input" - - local count - count="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')" - if [[ "$count" != "1" ]]; then - echo "expected archive to contain one top-level artifact directory: $input" >&2 - exit 1 - fi - - find "$extract_dir" -mindepth 1 -maxdepth 1 -type d -print -quit -} - -verify_artifact_dir() { - local artifact_dir="$1" - local manifest="$artifact_dir/manifest.json" - - if [[ ! -f "$manifest" ]]; then - echo "missing manifest: $manifest" >&2 - exit 1 - fi - - python3 - "$artifact_dir" "$manifest" <<'PY' -import hashlib -import json -import os -import sys - -artifact_dir, manifest_path = sys.argv[1:3] -with open(manifest_path, encoding="utf-8") as fh: - manifest = json.load(fh) - -required = [ - "schema_version", - "artifact_id", - "native_runtime_id", - "sdk_version", - "mesh_version", - "target_triple", - "platform", - "os", - "arch", - "backend", - "flavor", - "library", - "library_paths", - "library_sha256", - "requirements", - "features", -] -missing = [key for key in required if key not in manifest] -if missing: - raise SystemExit(f"missing manifest field(s): {', '.join(missing)}") - -if manifest["schema_version"] != 1: - raise SystemExit(f"unsupported schema_version: {manifest['schema_version']!r}") - -expected_artifact_id = f"meshllm-native-{manifest['platform']}-{manifest['flavor']}" -if manifest["artifact_id"] != expected_artifact_id: - raise SystemExit( - f"artifact_id does not match platform/flavor: {manifest['artifact_id']} != {expected_artifact_id}" - ) -if manifest["native_runtime_id"] != manifest["artifact_id"]: - raise SystemExit( - f"native_runtime_id must match artifact_id: {manifest['native_runtime_id']} != {manifest['artifact_id']}" - ) -if manifest["mesh_version"] != manifest["sdk_version"]: - raise SystemExit( - f"mesh_version must match sdk_version: {manifest['mesh_version']} != {manifest['sdk_version']}" - ) - -expected_os = { - "aarch64-apple-darwin": "macos", - "x86_64-apple-darwin": "macos", - "x86_64-unknown-linux-gnu": "linux", - "aarch64-unknown-linux-gnu": "linux", - "aarch64-linux-android": "linux", - "armv7-linux-androideabi": "linux", - "x86_64-linux-android": "linux", - "x86_64-pc-windows-msvc": "windows", -}.get(manifest["target_triple"]) -expected_arch = { - "aarch64-apple-darwin": "aarch64", - "x86_64-apple-darwin": "x86_64", - "x86_64-unknown-linux-gnu": "x86_64", - "aarch64-unknown-linux-gnu": "aarch64", - "aarch64-linux-android": "aarch64", - "armv7-linux-androideabi": "arm", - "x86_64-linux-android": "x86_64", - "x86_64-pc-windows-msvc": "x86_64", -}.get(manifest["target_triple"]) -if expected_os and manifest["os"] != expected_os: - raise SystemExit(f"os does not match target_triple: {manifest['os']} != {expected_os}") -if expected_arch and manifest["arch"] != expected_arch: - raise SystemExit(f"arch does not match target_triple: {manifest['arch']} != {expected_arch}") - -dir_name = os.path.basename(os.path.normpath(artifact_dir)) -if dir_name != manifest["artifact_id"]: - raise SystemExit(f"artifact directory name does not match artifact_id: {dir_name} != {manifest['artifact_id']}") - -library = manifest["library"] -if library not in manifest["library_paths"]: - raise SystemExit("library_paths must include the primary library") -if not isinstance(manifest["requirements"], list): - raise SystemExit("requirements must be a list") -for key, rel_path in (("library", library),): - if os.path.isabs(rel_path) or ".." in rel_path.split(os.sep): - raise SystemExit(f"{key} must be a relative path inside the artifact: {rel_path}") - path = os.path.join(artifact_dir, rel_path) - if not os.path.isfile(path): - raise SystemExit(f"missing {key}: {path}") - -library_path = os.path.join(artifact_dir, library) -with open(library_path, "rb") as fh: - actual = hashlib.sha256(fh.read()).hexdigest() -if actual != manifest["library_sha256"]: - raise SystemExit( - f"library_sha256 mismatch for {library}: {actual} != {manifest['library_sha256']}" - ) - -legacy_uniffi_library = manifest.get("uniffi_library") -if legacy_uniffi_library: - if os.path.isabs(legacy_uniffi_library) or ".." in legacy_uniffi_library.split(os.sep): - raise SystemExit( - f"uniffi_library must be a relative path inside the artifact: {legacy_uniffi_library}" - ) - legacy_path = os.path.join(artifact_dir, legacy_uniffi_library) - if not os.path.isfile(legacy_path): - raise SystemExit(f"missing uniffi_library: {legacy_path}") - with open(legacy_path, "rb") as fh: - legacy_actual = hashlib.sha256(fh.read()).hexdigest() - if legacy_actual != actual: - raise SystemExit( - f"uniffi_library checksum mismatch: {legacy_actual} != {actual}" - ) - -features = set(manifest["features"]) -for feature in ("mesh-inference", "model-management", "local-serving", "chat", "responses"): - if feature not in features: - raise SystemExit(f"missing feature marker: {feature}") - -platform = manifest["platform"] -library_name = os.path.basename(library) -if platform.startswith("darwin-") and not library_name.endswith(".dylib"): - raise SystemExit(f"darwin artifact must contain a dylib: {library_name}") -if (platform.startswith("linux-") or platform.startswith("android-")) and not library_name.endswith(".so"): - raise SystemExit(f"{platform} artifact must contain a .so: {library_name}") -if platform.startswith("windows-") and not library_name.endswith(".dll"): - raise SystemExit(f"windows artifact must contain a .dll: {library_name}") -PY - - echo "verified native SDK artifact: $artifact_dir" -} - -if [[ "$#" -lt 1 ]]; then - usage - exit 1 -fi - -for input in "$@"; do - artifact_dir="$(artifact_dir_for_input "$input")" - verify_artifact_dir "$artifact_dir" -done diff --git a/sdk/kotlin/example/example-jvm/src/main/kotlin/ai/meshllm/example/ExampleMain.kt b/sdk/kotlin/example/example-jvm/src/main/kotlin/ai/meshllm/example/ExampleMain.kt index 54c37aab63..aa343b57be 100644 --- a/sdk/kotlin/example/example-jvm/src/main/kotlin/ai/meshllm/example/ExampleMain.kt +++ b/sdk/kotlin/example/example-jvm/src/main/kotlin/ai/meshllm/example/ExampleMain.kt @@ -20,7 +20,7 @@ fun main(args: Array) = runBlocking { val inviteToken = args.firstOrNull { !it.startsWith("--") } ?: modelRef?.let { "local-kotlin-example" } ?: run { System.err.println("Usage: ExampleMain ") System.err.println("Or set MESH_SDK_MODEL_REF to run local serving.") - System.err.println("Set MESHLLM_NATIVE_RUNTIME_ARTIFACT_DIR to a verified meshllm-native-* artifact.") + System.err.println("Set MESHLLM_NATIVE_RUNTIME_ARTIFACT_DIR to a verified meshllm-native-runtime-* artifact.") return@runBlocking } diff --git a/sdk/node/README.md b/sdk/node/README.md index aad74d6cca..d6eb32dab3 100644 --- a/sdk/node/README.md +++ b/sdk/node/README.md @@ -113,9 +113,11 @@ Windows is supported through the same N-API addon shape: - addon: `mesh_llm_nodejs.node` - native runtime library: `meshllm_ffi.dll` - target triple: `x86_64-pc-windows-msvc` -- runtime artifact names: `meshllm-native-windows-x86_64-cpu`, - `meshllm-native-windows-x86_64-cuda`, `meshllm-native-windows-x86_64-rocm`, - or `meshllm-native-windows-x86_64-vulkan` +- runtime artifact names: `meshllm-native-runtime-windows-x86_64-cpu`, + `meshllm-native-runtime-windows-x86_64-cuda12`, + `meshllm-native-runtime-windows-x86_64-cuda13`, + `meshllm-native-runtime-windows-x86_64-rocm`, or + `meshllm-native-runtime-windows-x86_64-vulkan` The Windows release pipeline already builds CPU, CUDA, ROCm, and Vulkan runtime bundles. The Node SDK should publish matching prebuilt addon/runtime packages diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs index e8b47e78bb..81c80a1bed 100644 --- a/tools/xtask/src/main.rs +++ b/tools/xtask/src/main.rs @@ -1514,7 +1514,6 @@ fn check_docs_and_workflow_invariants(repo_root: &Path) -> DynResult<()> { fn check_release_dispatch_version_preparation(release_workflow: &str) -> DynResult<()> { const DISPATCH_RELEASE_JOBS: &[&str] = &[ "build", - "build_native_sdk_runtime", "build_swift_sdk_artifact", "build_linux_arm64", "build_linux_aarch64_cuda", From 22a9df999a51afa22696fedf7a40f6fdaab5820e Mon Sep 17 00:00:00 2001 From: James Dumay Date: Thu, 4 Jun 2026 12:35:33 +1000 Subject: [PATCH 6/7] support windows checksum tools in llama prep --- scripts/prepare-llama.sh | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/scripts/prepare-llama.sh b/scripts/prepare-llama.sh index 9c2b2c579d..c8ec1b331c 100755 --- a/scripts/prepare-llama.sh +++ b/scripts/prepare-llama.sh @@ -68,6 +68,33 @@ git_retry() { done } +sha256_file() { + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + elif command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + elif command -v certutil >/dev/null 2>&1; then + certutil -hashfile "$1" SHA256 | awk 'NR == 2 { gsub(/[[:space:]]/, ""); print tolower($0) }' + else + echo "shasum, sha256sum, or certutil is required" >&2 + exit 1 + fi +} + +sha256_stdin() { + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 | awk '{print $1}' + elif command -v sha256sum >/dev/null 2>&1; then + sha256sum | awk '{print $1}' + else + local tmp + tmp="$(mktemp)" + cat > "$tmp" + sha256_file "$tmp" + rm -f "$tmp" + fi +} + clone_llama_workdir() { local attempt=1 local max_attempts="${LLAMA_GIT_MAX_ATTEMPTS:-4}" @@ -129,11 +156,11 @@ compute_patch_digest() { ( for patch in "${PATCHES[@]}"; do rel="${patch#$PATCH_DIR/}" - checksum="$(shasum -a 256 "$patch" | awk '{print $1}')" + checksum="$(sha256_file "$patch")" printf '%s\n' "$rel" printf '%s\n' "$checksum" done - ) | shasum -a 256 | awk '{print $1}' + ) | sha256_stdin } PATCH_DIGEST="$(compute_patch_digest)" From 07d7db9977e40eb0d5c6416aa3655c6dac9615ab Mon Sep 17 00:00:00 2001 From: James Dumay Date: Thu, 4 Jun 2026 14:17:47 +1000 Subject: [PATCH 7/7] fix windows native runtime release packaging --- .github/workflows/release.yml | 4 ++-- scripts/package-native-runtime.sh | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9ec21b4bc..6c57467ae2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -652,8 +652,8 @@ jobs: # `choco install cuda` (used previously via install-windows-sdk.ps1) # pulls latest = CUDA 13.2, which deterministically crashes sccache # on nvcc output (see mozilla/sccache#2470). CI pins to the same - # version via Jimver/cuda-toolkit; mirror that here. - WINDOWS_CUDA_VERSION: ${{ vars.CUDA_VERSION || '12.9.2' }} + # version via Jimver/cuda-toolkit; use a version available in that action. + WINDOWS_CUDA_VERSION: ${{ vars.WINDOWS_CUDA_VERSION || '12.9.1' }} strategy: fail-fast: false matrix: diff --git a/scripts/package-native-runtime.sh b/scripts/package-native-runtime.sh index 87241fb4c2..e11c6d7d73 100755 --- a/scripts/package-native-runtime.sh +++ b/scripts/package-native-runtime.sh @@ -171,8 +171,10 @@ sha256_file() { shasum -a 256 "$1" | awk '{print $1}' elif command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' + elif command -v certutil >/dev/null 2>&1; then + certutil -hashfile "$1" SHA256 | awk 'NR == 2 { gsub(/[[:space:]]/, ""); print tolower($0) }' else - echo "shasum or sha256sum is required" >&2 + echo "shasum, sha256sum, or certutil is required" >&2 exit 1 fi } @@ -224,7 +226,7 @@ library_pattern() { primary_library_name() { case "$TARGET_TRIPLE" in *apple-darwin) printf 'libllama.dylib\n' ;; - *windows*) printf 'llama.dll\n' ;; + *windows*) printf 'libllama.dll\n' ;; *) printf 'libllama.so\n' ;; esac }