1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
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,
}
/// Installs all projects and shell scripts.
///
/// # Errors
///
/// Returns an `io::Error` if the current directory cannot be determined, if the project
/// installation fails, or if the shell scripts cannot be installed.
pub fn install_all(clean_before_build: bool) -> Result<(), std::io::Error> {
let current = std::env::current_dir()?;
install_this_project(¤t, clean_before_build)?;
install_shell_scripts()?;
Ok(())
}
/// Installs a project from the given path.
///
/// # Errors
///
/// Returns an `io::Error` if the project installation fails, e.g., if `cargo build`
/// fails, the Cargo.toml cannot be parsed, or file operations (copy, create dir) fail.
pub fn install_this_project(
current: &std::path::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::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::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(())
}
/// Installs shell completion scripts for the `mling` command.
///
/// # Errors
///
/// Returns an `io::Error` if the shell scripts cannot be built or installed.
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(())
}
|