Files
mize/core/src/lib.rs
Sebastian Moser 37cb94d1d4 ...
2026-10-23 15:27:31 +02:00

280 lines
8.3 KiB
Rust

#![allow(warnings)]
#![feature(str_as_str)]
use std::cell::RefCell;
use std::fmt::format;
#[macro_export]
macro_rules! out {
($($arg:tt)*) => {
// format_args! handles string interpolation safely at compile time
if let Err(e) = $crate::write_to_mize_stdout(format_args!($($arg)*)) {
// Choose your error handling behavior here (panic, print to stderr, or ignore)
eprintln!("out! macro failed to write!!");
}
};
}
pub fn write_to_mize_stdout(args: std::fmt::Arguments) -> MizeResult<()> {
println!("{}", args);
Ok(())
}
/// Mize Shell (msh) macro - wraps command execution with nixpkgs executable resolution.
///
/// If the executable is given as a bare name (identifier), it is resolved via
/// [`msh_get_nix_bin`] to a nixpkgs store path. If it is given as a string literal
/// containing `/`, it is treated as a path and used as-is.
///
/// # Examples
/// ```ignore
/// msh!(git status)?; // resolves `git` from nixpkgs
/// msh!(curl https://example.com)?;
/// msh!("/usr/bin/git" status)?; // uses path directly
/// ```
#[macro_export]
macro_rules! msh {
// First token is a string literal — check if it's a path at runtime
($cmd:literal $($rest:tt)*) => {{
let cmd_str: &str = $cmd;
let resolved: String = if cmd_str.contains('/') {
cmd_str.to_string()
} else {
$crate::msh_get_nix_bin(cmd_str)
};
let mut cmd = ::std::process::Command::new(resolved);
$(cmd.arg(::std::stringify!($rest));)*
let status = cmd.status()?;
if status.success() {
::std::result::Result::Ok(())
} else {
::std::result::Result::Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
::std::format!("msh: command exited with {}", status),
))
}
}};
// First token is an identifier — always resolve via nixpkgs
($cmd:ident $($rest:tt)*) => {{
let resolved: String = $crate::msh_get_nix_bin(::std::stringify!($cmd));
let mut cmd = ::std::process::Command::new(resolved);
$(cmd.arg(::std::stringify!($rest));)*
let status = cmd.status()?;
if status.success() {
::std::result::Result::Ok(())
} else {
::std::result::Result::Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
::std::format!("msh: command exited with {}", status),
))
}
}};
}
pub mod prelude {
pub use super::*;
pub use instance::Mize;
pub use id::*;
pub use error::MizeError;
pub use crate::mize_err;
pub use error::MizeResult;
pub use item::{Item, ItemData, IntoItemData};
pub use config::*;
pub use api::*;
pub use mize_macros::*;
}
pub use flume;
pub use tracing;
// the core part has the code that can run on any platform
pub mod config;
pub mod error;
pub mod id;
pub mod instance;
pub mod item;
pub mod memstore;
pub mod proto;
pub mod types;
pub mod c_api;
pub mod api;
pub use async_trait::async_trait;
pub use ciborium;
pub use error::MizeError;
pub use error::MizeResult;
pub use instance::Mize;
pub use instance::{
MizePart,
};
pub use mize_macros::*;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
/// Global Mize instance storage.
/// This is set once when the Mize instance is created and can be accessed from anywhere.
pub static GLOBAL_MIZE: OnceLock<Arc<Mutex<Mize>>> = OnceLock::new();
/// Get the global Mize instance.
/// Returns a clone of the Mize instance (which is cheap as it only clones Arcs internally).
pub fn get_global_mize() -> MizeResult<Mize> {
let arc = GLOBAL_MIZE
.get()
.ok_or_else(|| mize_err!("No global Mize instance set"))?;
let mize = arc
.lock()
.map_err(|_| mize_err!("Failed to lock global Mize"))?;
Ok(mize.clone())
}
/// Set the global Mize instance.
pub fn set_global_mize(mize: Mize) -> MizeResult<()> {
let arc = Arc::new(Mutex::new(mize.clone()));
if let Err(_) = GLOBAL_MIZE.set(arc) {
let a = GLOBAL_MIZE.get().unwrap();
let mut b = a.lock().map_err(|_| mize_err!("Failed to lock global Mize"))?;
*b = mize;
}
Ok(())
}
/// C API function to set the global Mize instance from a dynamically loaded part.
/// This is a hack to allow Rust parts loaded as .so files to access the global Mize instance.
#[unsafe(no_mangle)]
pub extern "C" fn mize_rust_hack_set_global(mize: Arc<Mutex<Mize>>) {
let _ = GLOBAL_MIZE.set(mize);
}
/// Resolve an executable name to its full path in the nixpkgs store.
///
/// Reads `nix.pkgs.ref` (nixpkgs commit hash) and `nix.bin` (path to the `nix`
/// binary) from the Mize config, then runs:
///
/// ```text
/// <nix.bin> eval --raw nixpkgs/<hash>#<pkg>
/// ```
///
/// and returns `<outPath>/bin/<executable>`.
///
/// Certain executable names are mapped to different nixpkgs attribute names
/// via a hardcoded match (e.g. `npm` → `nodejs`).
///
/// # Panics
/// Panics if the global Mize instance is not set, the config values are missing,
/// or `nix eval` fails.
pub fn msh_get_nix_bin(name: &str) -> String {
// Hardcoded mapping of executable names to nixpkgs attribute names
let pkg = match name {
"npm" => "nodejs",
"node" => "nodejs",
"yarn" => "yarn",
"pnpm" => "nodePackages.pnpm",
_ => name,
};
let mut mize = get_global_mize().expect("msh_get_nix_bin: no global Mize instance set");
let nixpkgs_ref = mize
.get_config("nix.pkgs.ref")
.expect("msh_get_nix_bin: failed to get config nix.pkgs.ref")
.value_string()
.expect("msh_get_nix_bin: nix.pkgs.ref is not a string");
let nix_bin = mize
.get_config("nix.bin")
.expect("msh_get_nix_bin: failed to get config nix.bin")
.value_string()
.expect("msh_get_nix_bin: nix.bin is not a string");
// Run: nix eval --raw nixpkgs/<hash>#<pkg>
// --raw outputs just the store path without quotes
let attr = format!("nixpkgs/{}#{}", nixpkgs_ref, pkg);
let output = std::process::Command::new(&nix_bin)
.args(["eval", "--raw", &attr])
.output()
.expect("msh_get_nix_bin: failed to spawn nix eval");
if !output.status.success() {
panic!(
"msh_get_nix_bin: nix eval failed for {}: {}",
attr,
String::from_utf8_lossy(&output.stderr)
);
}
let out_path = String::from_utf8(output.stdout)
.expect("msh_get_nix_bin: nix eval output is not valid UTF-8")
.trim()
.to_string();
format!("{}/bin/{}", out_path, name)
}
/// Run a command with Mize Shell executable resolution.
///
/// This is the function-based equivalent of the [`msh!`] macro, with proper
/// argument handling (no `stringify!` limitations).
///
/// If `cmd` contains `/` it is treated as a path and used as-is.
/// Otherwise it is resolved via [`msh_get_nix_bin`].
///
/// Returns `Ok(())` if the command exits successfully, or an `io::Error` otherwise.
pub fn msh_run(cmd: &str, args: &[&str]) -> std::io::Result<()> {
let resolved = if cmd.contains('/') {
cmd.to_string()
} else {
msh_get_nix_bin(cmd)
};
let mut command = std::process::Command::new(resolved);
command.args(args);
let status = command.status()?;
if status.success() {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("msh: command exited with {}", status),
))
}
}
// platform specific stuff
pub mod platform {
use crate::{Mize, MizeResult};
#[cfg(feature = "target-js")]
pub mod js;
#[cfg(feature = "target-os")]
pub mod os;
pub fn init(mize: &mut Mize) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
os::os_instance_init(mize)?;
}
#[cfg(feature = "target-js")] {
js::wasm_instance_init(mize)?;
}
Ok(())
}
pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
os::load_part(mize, part_name, path)?;
}
Ok(())
}
pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
#[cfg(feature = "target-os")] {
os::fetch_part(mize, part_name)?;
}
Ok("alsdkjf".to_owned())
}
}