Skip to main content

idt/installers/
alacritty.rs

1use std::path::Path;
2
3use rootcause::prelude::ResultExt as _;
4use ytil_cmd::silent_cmd;
5
6use crate::installers::Installer;
7
8pub struct Alacritty<'a> {
9    pub dev_tools_dir: &'a Path,
10    pub bin_dir: &'a Path,
11}
12
13impl Installer for Alacritty<'_> {
14    fn bin_name(&self) -> &'static str {
15        "alacritty"
16    }
17
18    /// Builds Alacritty from source, symlinks the binary into `bin_dir`, and
19    /// copies `Alacritty.app` into `/Applications` (atomic swap).
20    fn install(&self) -> rootcause::Result<()> {
21        let source_dir = self.dev_tools_dir.join(self.bin_name()).join("source");
22
23        silent_cmd("sh")
24            .args([
25                "-c",
26                &format!(
27                    r#"
28                        ([ ! -d "{0}" ] && \
29                            git clone --depth=1 https://github.com/alacritty/alacritty.git {0} || true) && \
30                        cd {0} && \
31                        git fetch origin master --depth=1 && \
32                        git checkout origin/master && \
33                        rustup toolchain install stable --profile default && \
34                        rustup override set stable && \
35                        make app
36                    "#,
37                    source_dir.display(),
38                ),
39            ])
40            .status()
41            .context("failed to spawn build command")?
42            .exit_ok()
43            .context("build failed")
44            .attach_with(|| format!("tool={}", self.bin_name()))
45            .attach_with(|| format!("source_dir={}", source_dir.display()))?;
46
47        let app = source_dir
48            .join("target")
49            .join("release")
50            .join("osx")
51            .join("Alacritty.app");
52
53        crate::installers::install_macos_app(&app, self.bin_dir, self.bin_name())?;
54
55        // Alacritty sets TERM=alacritty, but programs need a matching terminfo entry to
56        // know the terminal's capabilities. Without it the system falls back to TERM=dumb,
57        // which breaks tools that depend on a capable terminal (e.g. starship, neovim).
58        // The app bundle ships pre-compiled entries; copy them to ~/.terminfo/ (no sudo,
59        // bypasses macOS SIP on /usr/share, avoids tic compatibility issues).
60        let home = std::env::var("HOME").context("error reading HOME env var")?;
61        let terminfo_dest = Path::new(&home).join(".terminfo").join("61");
62        std::fs::create_dir_all(&terminfo_dest)
63            .context("error creating ~/.terminfo/61")
64            .attach_with(|| format!("path={}", terminfo_dest.display()))?;
65
66        let bundled = app.join("Contents").join("Resources").join("61");
67        for entry in &["alacritty", "alacritty-direct"] {
68            let src = bundled.join(entry);
69            let dst = terminfo_dest.join(entry);
70            std::fs::copy(&src, &dst)
71                .context("error copying terminfo entry")
72                .attach_with(|| format!("src={}", src.display()))
73                .attach_with(|| format!("dst={}", dst.display()))?;
74        }
75
76        Ok(())
77    }
78
79    fn health_check_args(&self) -> Option<&[&str]> {
80        Some(&["--version"])
81    }
82
83    fn should_verify_checksum(&self) -> bool {
84        false
85    }
86}