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
use anyhow::{anyhow, ensure, Result};
use dylint_internal::env::{self, var};
use semver::{Version, VersionReq};
use std::{
    fs::{copy, create_dir_all, write},
    path::{Path, PathBuf},
    process::Command,
};
use tempfile::tempdir;

const README_TXT: &str = r#"
This directory contains Rust compiler drivers used by Dylint
(https://github.com/trailofbits/dylint).

Deleting this directory will cause Dylint to rebuild the drivers
the next time it needs them, but will have no ill effects.
"#;

fn cargo_toml(toolchain: &str, dylint_driver_spec: &str) -> String {
    format!(
        r#"
[package]
name = "dylint_driver-{}"
version = "0.1.0"
edition = "2018"

[dependencies]
anyhow = "1.0.38"
env_logger = "0.8.3"
dylint_driver = {{ {} }}
"#,
        toolchain, dylint_driver_spec,
    )
}

const MAIN_RS: &str = r#"
use anyhow::Result;
use std::env;
use std::ffi::OsString;

pub fn main() -> Result<()> {
    env_logger::init();

    let args: Vec<_> = env::args().map(OsString::from).collect();

    dylint_driver::dylint_driver(&args)
}
"#;

#[allow(unknown_lints)]
#[allow(question_mark_in_expression)]
pub fn get(toolchain: &str) -> Result<PathBuf> {
    let dylint_drivers = dylint_drivers()?;

    let driver_dir = dylint_drivers.join(&toolchain);
    if !driver_dir.is_dir() {
        create_dir_all(&driver_dir)?;
    }

    let driver = driver_dir.join("dylint-driver");
    if !driver.exists() || is_outdated(&driver)? {
        build(toolchain, &driver)?;
    }

    Ok(driver)
}

fn dylint_drivers() -> Result<PathBuf> {
    if let Ok(dylint_driver_path) = var(env::DYLINT_DRIVER_PATH) {
        let dylint_drivers = Path::new(&dylint_driver_path);
        ensure!(dylint_drivers.is_dir());
        Ok(dylint_drivers.to_path_buf())
    } else {
        let home = var(env::HOME)?;
        let dylint_drivers = Path::new(&home).join(".dylint_drivers");
        if !dylint_drivers.is_dir() {
            create_dir_all(&dylint_drivers)?;
            write(dylint_drivers.join("README.txt"), README_TXT)?;
        }
        Ok(dylint_drivers)
    }
}

fn is_outdated(driver: &Path) -> Result<bool> {
    let output = Command::new(driver).args(&["-V"]).output()?;
    let stdout = std::str::from_utf8(&output.stdout)?;
    let theirs = stdout
        .trim_end()
        .rsplitn(2, ' ')
        .next()
        .ok_or_else(|| anyhow!("Could not parse driver version"))?;

    let their_version = Version::parse(theirs)?;
    let their_req = VersionReq::parse(theirs)?;

    let our_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
    let our_req = VersionReq::parse(env!("CARGO_PKG_VERSION"))?;

    Ok(their_req.matches(&our_version) && !our_req.matches(&their_version))
}

#[allow(clippy::assertions_on_constants)]
#[allow(clippy::expect_used)]
fn build(toolchain: &str, driver: &Path) -> Result<()> {
    let tempdir = tempdir()?;
    let package = tempdir.path();

    let version_spec = format!("version = \"={}\"", env!("CARGO_PKG_VERSION"));

    // smoelius: Fetch the `dylint_driver` package from crates.io if built in release mode or if
    // `dylint_driver_remote` is enabled.
    #[cfg(any(not(debug_assertions), feature = "dylint_driver_remote"))]
    let path_spec = "";
    #[cfg(all(debug_assertions, not(feature = "dylint_driver_remote")))]
    let path_spec = format!(
        ", path = \"{}\"",
        Path::new(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .expect("Could not get parent directory")
            .join("driver")
            .to_string_lossy()
    );

    let dylint_driver_spec = format!("{}{}", version_spec, path_spec);

    write(
        package.join("Cargo.toml"),
        cargo_toml(toolchain, &dylint_driver_spec),
    )?;
    let src = package.join("src");
    create_dir_all(&src)?;
    write(&src.join("main.rs"), MAIN_RS)?;

    dylint_internal::build()
        .envs(vec![
            (env::RUSTFLAGS, "-C rpath=yes"),
            (env::RUSTUP_TOOLCHAIN, toolchain),
        ])
        .current_dir(&package)
        .success()?;

    copy(
        package
            .join("target")
            .join("debug")
            .join(format!("dylint_driver-{}", toolchain)),
        driver,
    )?;

    Ok(())
}