Skip to main content

lux_cli/
shell.rs

1use clap::Args;
2use lux_lib::{
3    config::Config, lua_installation::LuaInstallation, path::Paths, progress::MultiProgress,
4    tree::InstallTree,
5};
6use miette::{miette, IntoDiagnostic, Result};
7use which::which;
8
9use std::{env, path::PathBuf};
10use tokio::process::Command;
11
12use super::workspace::current_workspace_or_user_tree;
13
14#[derive(Args)]
15pub struct Shell {
16    /// Add test dependencies to the shell's paths,{n}
17    /// in addition to the regular dependencies.
18    #[arg(long)]
19    test: bool,
20
21    /// Add *only* build dependencies to the shell's paths.
22    #[arg(long, conflicts_with = "test")]
23    build: bool,
24
25    /// Disable the Lux loader.{n}
26    /// If a rock has conflicting transitive dependencies,{n}
27    /// disabling the Lux loader may result in the wrong modules being loaded.{n}
28    #[arg(long)]
29    no_loader: bool,
30}
31
32pub async fn shell(data: Shell, config: Config) -> Result<()> {
33    if env::var("LUX_SHELL").is_ok_and(|lx_shell_var| lx_shell_var == "1") {
34        return Err(miette!("Already in a Lux shell."));
35    }
36
37    let tree = current_workspace_or_user_tree(&config)?;
38
39    let path = if data.build {
40        let build_tree_path = tree.build_tree(&config)?;
41        Paths::new(&build_tree_path)?
42    } else {
43        let mut path = Paths::new(&tree)?;
44        if data.test {
45            let test_tree_path = tree.test_tree(&config)?;
46            let test_path = Paths::new(&test_tree_path)?;
47            path.prepend(&test_path);
48        }
49        path
50    };
51
52    let shell: PathBuf = match env::var("SHELL") {
53        Ok(val) => PathBuf::from(val),
54        Err(_) => {
55            #[cfg(any(target_os = "linux", target_os = "android"))]
56            let fallback = which("bash")
57                .into_diagnostic()
58                .map_err(|_| miette!("Cannot find `bash` on your system!"))?;
59
60            #[cfg(target_os = "windows")]
61            let fallback = which("cmd.exe")
62                .into_diagnostic()
63                .map_err(|_| miette!("Cannot find `cmd.exe` on your system!"))?;
64
65            #[cfg(target_os = "macos")]
66            let fallback = which("zsh")
67                .into_diagnostic()
68                .map_err(|_| miette!("Cannot find `zsh` on your system!"))?;
69
70            fallback
71        }
72    };
73
74    let lua_path = path.package_path_prepended();
75    let lua_cpath = path.package_cpath_prepended();
76
77    let lua_init = if data.no_loader {
78        None
79    } else if tree.version().lux_lib_dir().is_none() {
80        eprintln!(
81            "⚠️ WARNING: lux-lua library not found.
82    Cannot use the `lux.loader`.
83    To suppress this warning, set the `--no-loader` option.
84                    "
85        );
86        None
87    } else {
88        Some(path.init())
89    };
90
91    let lua_version = tree.version();
92
93    let mut bin_path = path.path_prepended();
94
95    let progress = MultiProgress::new_arc(&config);
96    let bar = progress.map(|progress| progress.new_bar());
97    let lua = LuaInstallation::new(lua_version, &config, &bar).await?;
98    bar.map(|bar| bar.finish_and_clear());
99    if let Some(lua_bin_path) = lua.bin().as_ref().and_then(|lua_bin| lua_bin.parent()) {
100        bin_path.add_path(lua_bin_path.to_path_buf());
101    }
102
103    let _ = Command::new(&shell)
104        .env("PATH", bin_path.joined())
105        .env("LUA_PATH", lua_path.joined())
106        .env("LUA_CPATH", lua_cpath.joined())
107        .env("LUA_INIT", lua_init.unwrap_or_default())
108        .env("LUX_SHELL", "1")
109        .spawn()
110        .into_diagnostic()?
111        .wait()
112        .await
113        .into_diagnostic()?;
114
115    Ok(())
116}