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