Skip to main content

zj/
lib.rs

1#![feature(exit_status_error)]
2
3use std::path::Path;
4use std::path::PathBuf;
5
6use owo_colors::OwoColorize;
7use rootcause::prelude::ResultExt;
8
9const ZELLIJ_PLUGINS_PATH: &[&str] = &[".config", "zellij", "plugins"];
10const ZELLIJ_LAYOUTS_PATH: &[&str] = &[".config", "zellij", "layouts"];
11const WASM_TARGET: &str = "wasm32-wasip1";
12
13pub struct PluginInstallSpec {
14    pub dir_name: &'static str,
15    pub wasm_name: &'static str,
16}
17
18/// Build a Zellij WASM plugin and copy it into the local Zellij plugins directory.
19///
20/// # Errors
21/// Returns an error when the WASM target cannot be installed, the plugin build fails, or the artifact cannot be copied.
22pub fn build_and_install_plugin(spec: &PluginInstallSpec, is_debug: bool) -> rootcause::Result<()> {
23    build_and_install_plugin_copies(spec, &[spec.wasm_name], is_debug)
24}
25
26/// Build a Zellij WASM plugin once and copy it into the local Zellij plugins directory under multiple names.
27///
28/// # Errors
29/// Returns an error when the WASM target cannot be installed, the plugin build fails, or any artifact copy fails.
30pub fn build_and_install_plugin_copies(
31    spec: &PluginInstallSpec,
32    install_names: &[&str],
33    is_debug: bool,
34) -> rootcause::Result<()> {
35    let wasm_path = build_wasm(spec, is_debug)
36        .context("failed to build wasm plugin")
37        .attach_with(|| format!("plugin={}", spec.dir_name))?;
38    for install_name in install_names {
39        install_wasm_plugin(&wasm_path, install_name)
40            .context("failed to install wasm plugin")
41            .attach_with(|| format!("plugin={}", spec.dir_name))
42            .attach_with(|| format!("install_name={install_name}"))?;
43    }
44    Ok(())
45}
46
47/// Copy a Zellij layout file into the local Zellij layouts directory.
48///
49/// # Errors
50/// Returns an error when the install directory cannot be resolved or created, or the layout file cannot be copied.
51pub fn install_layout_file(source: &Path, layout_name: &str) -> rootcause::Result<()> {
52    let install_dir = ytil_sys::dir::build_home_path(ZELLIJ_LAYOUTS_PATH)
53        .context("failed to determine zellij layouts directory")
54        .attach_with(|| format!("layouts_path={ZELLIJ_LAYOUTS_PATH:?}"))?;
55
56    std::fs::create_dir_all(&install_dir)
57        .context("failed to create zellij layouts directory")
58        .attach_with(|| format!("install_dir={}", install_dir.display()))?;
59
60    let dest = install_dir.join(layout_name);
61    ytil_sys::file::atomic_cp(source, &dest)
62        .context("failed to copy layout to install location")
63        .attach_with(|| format!("from={}", source.display()))
64        .attach_with(|| format!("to={}", dest.display()))?;
65
66    println!("{} {} to {}", "Copied".green().bold(), source.display(), dest.display());
67    Ok(())
68}
69
70fn build_wasm(spec: &PluginInstallSpec, is_debug: bool) -> rootcause::Result<PathBuf> {
71    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
72    let plugin_dir = manifest_dir
73        .parent()
74        .ok_or_else(|| rootcause::report!("cannot resolve zj dir from CARGO_MANIFEST_DIR"))?
75        .join("plugins")
76        .join(spec.dir_name);
77    let workspace_target = manifest_dir
78        .parent()
79        .and_then(Path::parent)
80        .and_then(Path::parent)
81        .ok_or_else(|| rootcause::report!("cannot resolve workspace target from CARGO_MANIFEST_DIR"))?
82        .join("target");
83
84    build_wasm_plugin(&plugin_dir, &workspace_target, spec.wasm_name, is_debug)
85}
86
87fn build_wasm_plugin(
88    plugin_dir: &Path,
89    workspace_target_root: &Path,
90    artifact_name: &str,
91    is_debug: bool,
92) -> rootcause::Result<PathBuf> {
93    ytil_cmd::silent_cmd("rustup")
94        .args(["target", "add", WASM_TARGET])
95        .status()
96        .context("failed to spawn rustup command")
97        .attach_with(|| format!("target={WASM_TARGET}"))?
98        .exit_ok()
99        .context("failed to add wasm32-wasip1 target")
100        .attach_with(|| format!("target={WASM_TARGET}"))?;
101
102    let wasm_target = workspace_target_root.join("wasm-plugins");
103    let mut cmd = ytil_cmd::silent_cmd("cargo");
104    cmd.args(["build", "--target", WASM_TARGET]);
105    cmd.current_dir(plugin_dir);
106    cmd.env("CARGO_TARGET_DIR", &wasm_target);
107    if !is_debug {
108        cmd.arg("--release");
109    }
110    cmd.status()
111        .context("failed to spawn cargo build command")
112        .attach_with(|| format!("target={WASM_TARGET}"))?
113        .exit_ok()
114        .context("failed to build wasm plugin")
115        .attach_with(|| format!("target={WASM_TARGET}"))
116        .attach_with(|| format!("plugin_dir={}", plugin_dir.display()))?;
117
118    let profile = if is_debug { "debug" } else { "release" };
119    Ok(wasm_target.join(WASM_TARGET).join(profile).join(artifact_name))
120}
121
122fn install_wasm_plugin(built: &Path, install_name: &str) -> rootcause::Result<()> {
123    let install_dir = ytil_sys::dir::build_home_path(ZELLIJ_PLUGINS_PATH)
124        .context("failed to determine zellij plugins directory")
125        .attach_with(|| format!("plugins_path={ZELLIJ_PLUGINS_PATH:?}"))?;
126
127    std::fs::create_dir_all(&install_dir)
128        .context("failed to create install directory")
129        .attach_with(|| format!("install_dir={}", install_dir.display()))?;
130
131    let dest = install_dir.join(install_name);
132    ytil_sys::file::atomic_cp(built, &dest)
133        .context("failed to copy wasm plugin to install location")
134        .attach_with(|| format!("from={}", built.display()))
135        .attach_with(|| format!("to={}", dest.display()))?;
136
137    println!("{} {} to {}", "Copied".green().bold(), built.display(), dest.display());
138    Ok(())
139}