1#![feature(exit_status_error)]
11
12use std::collections::HashMap;
13use std::collections::HashSet;
14use std::path::Path;
15
16use owo_colors::OwoColorize;
17use rootcause::prelude::ResultExt;
18use rootcause::report;
19use ytil_sys::SysInfo;
20use ytil_sys::cli::Args;
21
22use crate::installers::Installer;
23use crate::installers::alacritty::Alacritty;
24use crate::installers::bash_language_server::BashLanguageServer;
25use crate::installers::cargo::Cargo;
26use crate::installers::commitlint::Commitlint;
27use crate::installers::deno::Deno;
28use crate::installers::docker_langserver::DockerLangServer;
29use crate::installers::eslint_d::EslintD;
30use crate::installers::graphql_lsp::GraphQlLsp;
31use crate::installers::hadolint::Hadolint;
32use crate::installers::helm_ls::HelmLs;
33use crate::installers::lua_ls::LuaLanguageServer;
34use crate::installers::marksman::Marksman;
35use crate::installers::nvim::Nvim;
36use crate::installers::opencode::Opencode;
37use crate::installers::prettierd::PrettierD;
38use crate::installers::quicktype::Quicktype;
39use crate::installers::rio::Rio;
40use crate::installers::ruff_lsp::RuffLsp;
41use crate::installers::shellcheck::Shellcheck;
42use crate::installers::sql_language_server::SqlLanguageServer;
43use crate::installers::sqruff::Sqruff;
44use crate::installers::starship::Starship;
45use crate::installers::terraform_ls::TerraformLs;
46use crate::installers::typescript_language_server::TypescriptLanguageServer;
47use crate::installers::typos_lsp::TyposLsp;
48use crate::installers::vscode_langservers::VsCodeLangServers;
49use crate::installers::yaml_language_server::YamlLanguageServer;
50use crate::installers::zellij::Zellij;
51
52mod downloaders;
53mod installers;
54
55#[ytil_sys::main]
57fn main() -> rootcause::Result<()> {
58 let args = ytil_sys::cli::get();
59 if args.has_help() {
60 println!(include_str!("../help.txt"));
61 return Ok(());
62 }
63 println!(
64 "{:#?} started with args {}",
65 std::env::current_exe()?.bold().cyan(),
66 format!("{args:#?}").white().bold()
67 );
68
69 let dev_tools_dir = args
70 .first()
71 .ok_or_else(|| {
72 eprintln!("{}", include_str!("../help.txt"));
73 report!("missing dev_tools_dir arg")
74 })
75 .attach_with(|| format!("args={args:#?}"))?
76 .trim_end_matches('/');
77 let bin_dir = args
78 .get(1)
79 .ok_or_else(|| {
80 eprintln!("{}", include_str!("../help.txt"));
81 report!("missing bin_dir arg")
82 })
83 .attach_with(|| format!("args={args:#?}"))?
84 .trim_end_matches('/');
85 let supplied_bin_names: Vec<&str> = args.iter().skip(2).map(AsRef::as_ref).collect();
86
87 let sys_info = SysInfo::get()?;
88
89 let dev_tools_path = Path::new(dev_tools_dir);
90 let bin_path = Path::new(bin_dir);
91 std::fs::create_dir_all(dev_tools_path)?;
92 std::fs::create_dir_all(bin_path)?;
93
94 let cargo_bin_dir = crate::installers::cargo::bin_dir()?;
95 let parallel_installers = parallel_installers(dev_tools_path, bin_path, &sys_info);
96 let managed_cargo_installers = managed_cargo_installers(&cargo_bin_dir, bin_path);
97 let (selected_parallel_installers, selected_cargo_installers, unknown_bin_names) =
98 select_installers(&supplied_bin_names, ¶llel_installers, &managed_cargo_installers);
99
100 if !unknown_bin_names.is_empty() {
101 eprintln!(
102 "{} bins without matching installers",
103 format!("{unknown_bin_names:#?}").yellow().bold()
104 );
105 }
106
107 let installers_res = run_installers(selected_parallel_installers, selected_cargo_installers);
108
109 if let Err(errors) = report(&installers_res) {
110 eprintln!(
111 "{} | errors_count={} bin_names={errors:#?}",
112 "error installing tools".red(),
113 errors.len()
114 );
115 std::process::exit(1);
116 }
117
118 ytil_sys::rm::rm_dead_symlinks(bin_dir)?;
119
120 Ok(())
121}
122
123fn parallel_installers<'a>(
125 dev_tools_dir: &'a Path,
126 bin_dir: &'a Path,
127 sys_info: &'a SysInfo,
128) -> Vec<Box<dyn Installer + 'a>> {
129 vec![
130 Box::new(Alacritty { dev_tools_dir, bin_dir }),
131 Box::new(BashLanguageServer { dev_tools_dir, bin_dir }),
132 Box::new(Commitlint { dev_tools_dir, bin_dir }),
133 Box::new(Deno { bin_dir, sys_info }),
134 Box::new(DockerLangServer { dev_tools_dir, bin_dir }),
135 Box::new(EslintD { dev_tools_dir, bin_dir }),
136 Box::new(GraphQlLsp { dev_tools_dir, bin_dir }),
137 Box::new(Hadolint { bin_dir, sys_info }),
138 Box::new(HelmLs { bin_dir, sys_info }),
139 Box::new(LuaLanguageServer {
140 dev_tools_dir,
141 sys_info,
142 }),
143 Box::new(Marksman { bin_dir, sys_info }),
144 Box::new(Nvim { dev_tools_dir, bin_dir }),
145 Box::new(Opencode { bin_dir, sys_info }),
146 Box::new(PrettierD { dev_tools_dir, bin_dir }),
147 Box::new(Quicktype { dev_tools_dir, bin_dir }),
148 Box::new(Rio { dev_tools_dir, bin_dir }),
149 Box::new(RuffLsp { dev_tools_dir, bin_dir }),
150 Box::new(Shellcheck { bin_dir, sys_info }),
151 Box::new(Sqruff { bin_dir, sys_info }),
152 Box::new(SqlLanguageServer { dev_tools_dir, bin_dir }),
153 Box::new(Starship { dev_tools_dir, bin_dir }),
154 Box::new(TerraformLs { bin_dir, sys_info }),
155 Box::new(TypescriptLanguageServer { dev_tools_dir, bin_dir }),
156 Box::new(TyposLsp { bin_dir, sys_info }),
157 Box::new(VsCodeLangServers { dev_tools_dir, bin_dir }),
158 Box::new(YamlLanguageServer { dev_tools_dir, bin_dir }),
159 Box::new(Zellij { dev_tools_dir, bin_dir }),
160 ]
161}
162
163fn managed_cargo_installers<'a>(cargo_bin_dir: &'a Path, bin_dir: &'a Path) -> Vec<Box<dyn Installer + 'a>> {
165 vec![
166 Box::new(Cargo::registry_with_cargo_subcommand(
167 cargo_bin_dir,
168 bin_dir,
169 "cargo-auditable",
170 "cargo-auditable",
171 "auditable",
172 )),
173 Box::new(Cargo::registry_with_features(
174 cargo_bin_dir,
175 bin_dir,
176 "cargo-audit",
177 "cargo-audit",
178 "fix",
179 )),
180 Box::new(Cargo::registry_with_cargo_subcommand(
181 cargo_bin_dir,
182 bin_dir,
183 "cargo-diet",
184 "cargo-diet",
185 "diet",
186 )),
187 Box::new(Cargo::registry(
188 cargo_bin_dir,
189 bin_dir,
190 "cargo-machete",
191 "cargo-machete",
192 )),
193 Box::new(Cargo::registry_with_cargo_subcommand(
194 cargo_bin_dir,
195 bin_dir,
196 "cargo-make",
197 "cargo-make",
198 "make",
199 )),
200 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "cargo-sort", "cargo-sort")),
201 Box::new(Cargo::registry_with_cargo_subcommand(
202 cargo_bin_dir,
203 bin_dir,
204 "cargo-sort-derives",
205 "cargo-sort-derives",
206 "sort-derives",
207 )),
208 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "ccase", "ccase")),
209 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "fd", "fd-find")),
210 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "jnv", "jnv")),
211 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "mise", "mise")),
212 Box::new(Cargo::registry_with_health_check_args(
213 cargo_bin_dir,
214 bin_dir,
215 "pv",
216 "pv",
217 &["--help"],
218 )),
219 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "qj", "qj")),
220 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "rg", "ripgrep")),
221 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "sd", "sd")),
222 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "sqlx", "sqlx-cli")),
223 Box::new(Cargo::registry(
224 cargo_bin_dir,
225 bin_dir,
226 "tree-sitter",
227 "tree-sitter-cli",
228 )),
229 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "typos", "typos-cli")),
230 Box::new(Cargo::registry_with_all_features(
233 cargo_bin_dir,
234 bin_dir,
235 "taplo",
236 "taplo-cli",
237 )),
238 Box::new(Cargo::nightly_git(
239 cargo_bin_dir,
240 bin_dir,
241 "rust-analyzer",
242 "https://github.com/rust-lang/rust-analyzer.git",
243 "master",
244 )),
245 Box::new(Cargo::locked_registry(
246 cargo_bin_dir,
247 bin_dir,
248 "cargo-nextest",
249 "cargo-nextest",
250 )),
251 Box::new(Cargo::registry_with_cargo_subcommand(
252 cargo_bin_dir,
253 bin_dir,
254 "cargo-llvm-cov",
255 "cargo-llvm-cov",
256 "llvm-cov",
257 )),
258 Box::new(Cargo::git(
259 cargo_bin_dir,
260 bin_dir,
261 "rtk",
262 "https://github.com/rtk-ai/rtk",
263 )),
264 Box::new(Cargo::registry(cargo_bin_dir, bin_dir, "kache", "kache")),
265 ]
266}
267
268fn select_installers<'installer, 'name>(
270 supplied_bin_names: &[&'name str],
271 parallel_installers: &'installer [Box<dyn Installer + 'installer>],
272 managed_cargo_installers: &'installer [Box<dyn Installer + 'installer>],
273) -> (
274 Vec<&'installer dyn Installer>,
275 Vec<&'installer dyn Installer>,
276 Vec<&'name str>,
277) {
278 if supplied_bin_names.is_empty() {
279 return (
280 parallel_installers.iter().map(Box::as_ref).collect(),
281 managed_cargo_installers.iter().map(Box::as_ref).collect(),
282 vec![],
283 );
284 }
285
286 let parallel_installer_map: HashMap<&str, &dyn Installer> = parallel_installers
287 .iter()
288 .map(|installer| (installer.bin_name(), installer.as_ref()))
289 .collect();
290 let cargo_installer_map: HashMap<&str, &dyn Installer> = managed_cargo_installers
291 .iter()
292 .map(|installer| (installer.bin_name(), installer.as_ref()))
293 .collect();
294
295 let mut selected_parallel_installers = Vec::with_capacity(supplied_bin_names.len());
296 let mut selected_cargo_installers = Vec::with_capacity(supplied_bin_names.len());
297 let mut unknown_installers = vec![];
298 let mut selected_bin_names = HashSet::new();
299 for chosen_bin in supplied_bin_names {
300 if *chosen_bin == "cargo" {
301 for installer in managed_cargo_installers {
302 if selected_bin_names.insert(installer.bin_name()) {
303 selected_cargo_installers.push(installer.as_ref());
304 }
305 }
306 } else if let Some(&installer) = parallel_installer_map.get(chosen_bin) {
307 if selected_bin_names.insert(installer.bin_name()) {
308 selected_parallel_installers.push(installer);
309 }
310 } else if let Some(&installer) = cargo_installer_map.get(chosen_bin) {
311 if selected_bin_names.insert(installer.bin_name()) {
312 selected_cargo_installers.push(installer);
313 }
314 } else {
315 unknown_installers.push(*chosen_bin);
316 }
317 }
318
319 (
320 selected_parallel_installers,
321 selected_cargo_installers,
322 unknown_installers,
323 )
324}
325
326fn run_installers<'a>(
328 selected_parallel_installers: Vec<&'a dyn Installer>,
329 selected_cargo_installers: Vec<&'a dyn Installer>,
330) -> Vec<(&'a str, std::thread::Result<rootcause::Result<()>>)> {
331 std::thread::scope(|scope| {
332 let mut handles = Vec::with_capacity(selected_parallel_installers.len());
333 for installer in selected_parallel_installers {
334 handles.push((installer.bin_name(), scope.spawn(move || installer.run())));
335 }
336 let cargo_handle = scope.spawn(move || {
337 selected_cargo_installers
338 .into_iter()
339 .map(|installer| (installer.bin_name(), installer.run()))
340 .collect::<Vec<_>>()
341 });
342
343 let mut results = Vec::with_capacity(handles.len());
344 for (bin_name, handle) in handles {
345 results.push((bin_name, handle.join()));
346 }
347 match cargo_handle.join() {
348 Ok(cargo_results) => results.extend(
349 cargo_results
350 .into_iter()
351 .map(|(bin_name, result)| (bin_name, Ok(result))),
352 ),
353 Err(error) => results.push(("cargo", Err(error))),
354 }
355 results
356 })
357}
358
359fn report<'a>(installers_res: &'a [(&'a str, std::thread::Result<rootcause::Result<()>>)]) -> Result<(), Vec<&'a str>> {
364 let mut errors_bins = vec![];
365
366 for (bin_name, result) in installers_res {
367 match result {
368 Err(err) => {
369 eprintln!(
370 "{} installer thread panicked error={}",
371 bin_name.red(), format!("{err:#?}").red()
373 );
374 errors_bins.push(*bin_name);
375 }
376 Ok(Err(_)) => errors_bins.push(bin_name),
377 Ok(Ok(())) => {}
378 }
379 }
380
381 if errors_bins.is_empty() {
382 return Ok(());
383 }
384 Err(errors_bins)
385}