dylint_internal 6.0.0

Dylint internals
Documentation
use anyhow::{Context, Result, anyhow, ensure};
use regex::Regex;
use std::{
    ffi::OsStr,
    fs::File,
    io::{BufRead, BufReader},
    path::{Path, PathBuf},
    process::{Command, Stdio},
    sync::LazyLock,
    thread,
};

fn main() -> Result<()> {
    let toolchains = collect_toolchains(&["cargo-dylint", "examples", "expensive", "internal"])?;

    println!("{:#?}", &toolchains);

    let mut handles = Vec::new();
    for toolchain in ["stable", "nightly"] {
        handles.push(thread::spawn(move || install_toolchain(toolchain)));
    }
    for toolchain in toolchains {
        handles.push(thread::spawn(move || install_toolchain(toolchain)));
    }

    for handle in handles {
        let () = handle
            .join()
            .map_err(|error| anyhow!("{error:?}"))
            .and_then(std::convert::identity)?;
    }

    Ok(())
}

fn collect_toolchains(dirs: &[&str]) -> Result<Vec<String>> {
    let mut toolchains = Vec::new();

    for dir in dirs {
        let toolchains_for_dir = collect_toolchains_for_dir(dir)?;
        toolchains.extend(toolchains_for_dir);
    }

    toolchains.sort();
    toolchains.dedup();

    Ok(toolchains)
}

fn collect_toolchains_for_dir(dir: &str) -> Result<Vec<String>> {
    let mut ls_files = Command::new("git")
        .args(["ls-files", dir])
        .stdout(Stdio::piped())
        .spawn()
        .with_context(|| "Could not spawn `git ls-files`")?;

    let stdout = ls_files.stdout.take().unwrap();
    let mut toolchains = Vec::new();
    for result in BufReader::new(stdout).lines() {
        let path = result
            .map(PathBuf::from)
            .with_context(|| "Could not read from `git ls-files`")?;
        if path
            .file_stem()
            .and_then(OsStr::to_str)
            .is_some_and(|file_stem| file_stem.ends_with("_no_preinstall"))
        {
            continue;
        }
        let toolchains_for_path = collect_toolchains_for_path(path)?;
        toolchains.extend(toolchains_for_path);
    }
    Ok(toolchains)
}

static RE: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"\<nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}\>").unwrap());

fn collect_toolchains_for_path(path: impl AsRef<Path>) -> Result<Vec<String>> {
    let file = File::open(&path)
        .with_context(|| format!("Could not open `{}`", path.as_ref().display()))?;
    let mut toolchains = Vec::new();
    for result in BufReader::new(file).lines() {
        let line =
            result.with_context(|| format!("Could not read from `{}`", path.as_ref().display()))?;
        let n = line.find("//").unwrap_or(line.len());
        toolchains.extend(RE.find_iter(&line[..n]).map(|m| m.as_str().to_owned()));
    }
    Ok(toolchains)
}

fn install_toolchain(toolchain: impl AsRef<str>) -> Result<()> {
    let toolchain = toolchain.as_ref();

    let status = Command::new("rustup")
        .args(["install", toolchain, "--profile=minimal"])
        .status()
        .with_context(|| format!("Could not install {toolchain} with `rustup`"))?;
    ensure!(status.success());

    let status = Command::new("rustup")
        .args([
            "component",
            "add",
            "llvm-tools-preview",
            "rustc-dev",
            "--toolchain",
            toolchain,
        ])
        .status()
        .with_context(|| format!("Could not add components to {toolchain} with `rustup`"))?;
    ensure!(status.success());

    Ok(())
}