280 lines
8.3 KiB
Rust
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())
|
|
}
|
|
}
|