aboutsummaryrefslogtreecommitdiff
path: root/mling/src/project_installer.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-28 16:18:12 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-28 16:18:12 +0800
commit881e7399b2417c32fa996d94c6b389c1e06d8eb1 (patch)
treefd88cb181e9c5a0bae8677c43dff859f4cd82d80 /mling/src/project_installer.rs
parentdbc811d84fd809ea606a8bbed84b3e78e8cda334 (diff)
Add scaffolding CLI tool `mling`
Diffstat (limited to 'mling/src/project_installer.rs')
-rw-r--r--mling/src/project_installer.rs153
1 files changed, 153 insertions, 0 deletions
diff --git a/mling/src/project_installer.rs b/mling/src/project_installer.rs
new file mode 100644
index 0000000..d004e40
--- /dev/null
+++ b/mling/src/project_installer.rs
@@ -0,0 +1,153 @@
+use std::path::PathBuf;
+
+use mingling::{ShellFlag, build::build_comp_script_to};
+
+use crate::{
+ namespace_manager::{bin_dir, comp_dir, exe_path, working_dir},
+ project_solver::solve,
+};
+
+const SCRIPT_LOAD_BASH: &str = include_str!("../tmpl/load.sh");
+const SCRIPT_LOAD_FISH: &str = include_str!("../tmpl/load.fish");
+const SCRIPT_LOAD_PWSH: &str = include_str!("../tmpl/load.ps1");
+
+#[derive(serde::Deserialize)]
+struct CargoToml {
+ package: Package,
+}
+
+#[derive(serde::Deserialize)]
+struct Package {
+ name: String,
+}
+
+pub fn install_all(clean_before_build: bool) -> Result<(), std::io::Error> {
+ let current = std::env::current_dir()?;
+ install_this_project(current, clean_before_build)?;
+ install_shell_scripts()?;
+ Ok(())
+}
+
+pub fn install_this_project(
+ current: PathBuf,
+ clean_before_build: bool,
+) -> Result<(), std::io::Error> {
+ // Obtain context data
+ let solved = solve(current)?;
+
+ let workspace_root = &solved.workspace_root;
+
+ // If clean_before_build, execute cargo clean in workspace_root first
+ if clean_before_build {
+ let status = std::process::Command::new("cargo")
+ .arg("clean")
+ .current_dir(workspace_root)
+ .status()?;
+ if !status.success() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "exec `cargo clean` failed",
+ ));
+ }
+ }
+
+ // Execute cargo build --release in workspace_root
+ let status = std::process::Command::new("cargo")
+ .args(["build", "--release"])
+ .current_dir(workspace_root)
+ .status()?;
+ if !status.success() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "cargo build --release failed",
+ ));
+ }
+
+ // Parse package.name from workspace_root's Cargo.toml as namespace
+ let cargo_toml_content = std::fs::read_to_string(workspace_root.join("Cargo.toml"))?;
+ let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content).map_err(|e| {
+ std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ format!("failed to parse Cargo.toml: {e}"),
+ )
+ })?;
+ let namespace = cargo_toml.package.name;
+
+ // Ensure destination directories exist
+ std::fs::create_dir_all(bin_dir(namespace.clone()))?;
+ std::fs::create_dir_all(comp_dir(namespace.clone()))?;
+
+ // Copy binaries to corresponding exe_path
+ for bin in &solved.binaries {
+ let dst = exe_path(namespace.clone(), bin.name.clone());
+ std::fs::copy(&bin.path, &dst)?;
+ }
+
+ // Copy all completion scripts containing _comp from target/release to comp_dir
+ let target_dir = &solved.target_dir;
+ let release_dir = target_dir.join("release");
+ if release_dir.exists() {
+ for entry in std::fs::read_dir(&release_dir)? {
+ let entry = entry?;
+ let file_name = entry.file_name();
+ let file_name_str = file_name.to_string_lossy();
+ if file_name_str.contains("_comp") {
+ let dest = comp_dir(namespace.clone()).join(file_name.as_os_str());
+ std::fs::copy(entry.path(), &dest)?;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+pub fn install_shell_scripts() -> Result<(), std::io::Error> {
+ // Get the working directory (mingling data dir)
+ let wdir = working_dir();
+ std::fs::create_dir_all(&wdir)?;
+
+ // Build shell completion scripts for the "mling" command based on the current OS
+ let mling_comp = if cfg!(target_os = "windows") {
+ vec![ShellFlag::Powershell]
+ } else if cfg!(target_os = "macos") || cfg!(target_os = "linux") {
+ vec![ShellFlag::Bash, ShellFlag::Zsh, ShellFlag::Fish]
+ } else {
+ vec![ShellFlag::Bash]
+ };
+
+ for flag in mling_comp {
+ build_comp_script_to(
+ &flag,
+ "mling",
+ wdir.join(".comp").display().to_string().as_str(),
+ )?;
+ }
+
+ // Determine which scripts to write based on platform
+ let scripts: Vec<(&str, &str)> = if cfg!(target_os = "windows") {
+ vec![("load.ps1", SCRIPT_LOAD_PWSH)]
+ } else if cfg!(target_os = "macos") || cfg!(target_os = "linux") {
+ vec![
+ ("load.sh", SCRIPT_LOAD_BASH),
+ ("load.fish", SCRIPT_LOAD_FISH),
+ ]
+ } else {
+ // Fallback: write bash script
+ vec![("load.sh", SCRIPT_LOAD_BASH)]
+ };
+
+ for (filename, content) in scripts {
+ let dest = wdir.join(filename);
+ std::fs::write(&dest, content)?;
+ if cfg!(target_os = "linux") {
+ let status = std::process::Command::new("chmod")
+ .args(["+x", &dest.to_string_lossy()])
+ .status()?;
+ if !status.success() {
+ eprintln!("Failed to chmod {}", filename);
+ }
+ }
+ }
+
+ Ok(())
+}