prefer-dynamic 0.2.1

Copies the pre-compiled dynamic `std` library to your target directory, for `-Cprefer-dynamic` and `dylib` crates
use std::env::{var, var_os};
use std::ffi::OsStr;
use std::fmt;
use std::fs::copy;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::str::Utf8Error;

fn main() -> Result<(), Error> {
    println!("cargo::rerun-if-changed=build.rs");
    println!("cargo::rerun-if-env-changed=CARGO_ENCODED_RUSTFLAGS");
    println!("cargo::rerun-if-env-changed=OUT_DIR");
    println!("cargo::rerun-if-env-changed=RUSTC");
    println!("cargo::rerun-if-env-changed=TARGET");

    if cfg!(not(any(unix, windows))) {
        return Ok(());
    }

    let out_dir = env_path("OUT_DIR")?;
    let rustc = env_path("RUSTC")?;
    let target = env_path("TARGET")?;

    let mut dest_lib_dir = out_dir.as_path();
    if !out_dir.join("deps").is_dir() {
        for _ in 0..3 {
            match dest_lib_dir.parent() {
                Some(p) => dest_lib_dir = p,
                None => return Err(Error::OutdirParents(out_dir)),
            }
        }
    }

    let mut command = Command::new(rustc);
    command
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .arg("--print=target-libdir")
        .arg("--print=cfg")
        .arg("--target")
        .arg(target);
    if let Ok(args) = var("CARGO_ENCODED_RUSTFLAGS") {
        for arg in args.split('\x1f') {
            command.arg(arg);
        }
    }
    let output = command.output().map_err(Error::RustcRun)?;
    if !output.status.success() {
        return Err(Error::RustcExit(output.status));
    }
    let output = { output }.stdout;
    let output = std::str::from_utf8(&output).map_err(Error::RustcUtf8)?;
    let mut output = output.lines();

    let lib_path = output.next().map(Path::new).ok_or(Error::NoTargetLibdir)?;
    let mut lib_ext = "so";
    for line in output {
        lib_ext = match line {
            r#"target_os="linux""# => break,
            r#"target_vendor="apple""# => "dylib",
            r#"target_os="windows""# => "dll",
            _ => continue,
        };
        break;
    }

    for lib in lib_path
        .read_dir()
        .map_err(|err| Error::ReadDir(err, lib_path.to_owned()))?
    {
        let Ok(lib) = lib else {
            continue;
        };

        let file_name = PathBuf::from(lib.file_name());
        if file_name.extension() != Some(OsStr::new(lib_ext)) {
            continue;
        }
        let Some(str_filename) = file_name.to_str() else {
            continue;
        };
        if !str_filename
            .strip_prefix("lib")
            .unwrap_or(str_filename)
            .starts_with("std-")
        {
            continue;
        }

        let dest = dest_lib_dir.join(&file_name);
        if let Ok(metadata) = dest.symlink_metadata() {
            if metadata.is_dir() || metadata.is_symlink() {
                return Ok(());
            }
        }

        let src = lib_path.join(file_name);

        #[cfg(unix)]
        if std::os::unix::fs::symlink(&src, &dest).is_ok() {
            return Ok(());
        }

        copy(&src, &dest).map_err(|err| Error::Copy(err, src, dest))?;

        return Ok(());
    }
    Err(Error::NotFound(lib_path.to_owned(), lib_ext))
}

fn env_path(key: &'static str) -> Result<PathBuf, Error> {
    var_os(key).map(PathBuf::from).ok_or(Error::EnvVar(key))
}

enum Error {
    EnvVar(&'static str),
    OutdirParents(PathBuf),
    RustcRun(std::io::Error),
    RustcExit(ExitStatus),
    RustcUtf8(Utf8Error),
    NoTargetLibdir,
    ReadDir(std::io::Error, PathBuf),
    Copy(std::io::Error, PathBuf, PathBuf),
    NotFound(PathBuf, &'static str),
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::EnvVar(_)
            | Error::OutdirParents(_)
            | Error::RustcExit(_)
            | Error::NoTargetLibdir
            | Error::NotFound(_, _) => None,
            Error::RustcUtf8(err) => Some(err),
            Error::RustcRun(err) | Error::ReadDir(err, _) | Error::Copy(err, _, _) => Some(err),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::EnvVar(path) => {
                write!(
                    f,
                    "Environment variable `{}` is unset.",
                    path.escape_debug(),
                )
            }
            Self::OutdirParents(path) => {
                write!(f, "Unexpected `OUT_DIR` layout `{}`.", path.display())
            }
            Self::RustcRun(err) => {
                writeln!(f, "Could not run `target-libdir` and `cfg` query.\n{err}")
            }
            Self::RustcExit(status) => {
                write!(f, "Query for `target-libdir` and `cfg` returned {status}.")
            }
            Self::RustcUtf8(err) => {
                write!(
                    f,
                    "Query for `target-libdir` and `cfg` returned non-UTF-8 data.\n{err}",
                )
            }
            Self::NoTargetLibdir => {
                write!(f, "Query for `target-libdir` did not return any output.")
            }
            Self::ReadDir(err, path) => {
                write!(
                    f,
                    "Could not read directory entries of `target-libdir` `{}`.\n{err}",
                    path.display(),
                )
            }
            Self::Copy(err, src, dest) => {
                write!(
                    f,
                    "Could not copy file from `{}` to `{}`.\n{err}",
                    src.display(),
                    dest.display(),
                )
            }
            Self::NotFound(path, ext) => {
                write!(
                    f,
                    "Could not find `libstd-*.{ext}` in `{}`.",
                    path.display(),
                )
            }
        }
    }
}