dungeon-cell 0.1.0

Store (almost) any value as one type without dynamic memory.
Documentation
use std::env;
use std::ffi::OsString;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering};

const RT_CHECK_ENV: &str = "DUNGEON_CELL_RUNTIME_CHECKS";
const NO_CONST_TYPE_NAME: &str = "DUNGEON_CELL_NO_CONST_TYPE_NAME";

fn main() {
    // Sanity check that probe works.
    assert!(probe(""));

    let const_type_name_override =
        std::env::var_os(NO_CONST_TYPE_NAME).is_some();

    let const_assert_override = env::var_os(RT_CHECK_ENV).is_some();

    if !const_type_name_override
        && probe("pub const PROBE: () = ((), core::any::type_name::<()>()).0;")
    {
        emit("has_const_type_name");
    } else if !const_type_name_override
        && probe(
            r#"
        #![feature(const_type_name)]
        pub const PROBE: () = ((), core::any::type_name::<()>()).0;
        "#,
        )
    {
        emit("has_feature_const_type_name");
        emit("has_const_type_name");
    }

    if !const_assert_override {
        emit("use_const_assert");
    }

    rerun_env(RT_CHECK_ENV);
    rerun_env(NO_CONST_TYPE_NAME);
    rerun_path(file!());
}

// ----------

// Following is modified from https://github.com/cuviper/autocfg/blob/master/src/lib.rs
// Original code is licensed under Apache or MIT.

pub fn rerun_path(path: &str) {
    println!("cargo:rerun-if-changed={}", path);
}

pub fn rerun_env(var: &str) {
    println!("cargo:rerun-if-env-changed={}", var);
}

fn emit(cfg: &str) {
    println!("cargo:rustc-cfg={}", cfg);
}

fn rustc_path() -> PathBuf {
    env::var_os("RUSTC")
        .unwrap_or_else(|| "rustc".into())
        .into()
}

fn out_dir() -> PathBuf {
    env::var_os("OUT_DIR").unwrap().into()
}

fn target() -> Option<OsString> {
    env::var_os("TARGET")
}

fn rustflags() -> Vec<String> {
    let flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or("".to_owned());

    if flags.is_empty() {
        Vec::new()
    } else {
        flags.split('\x1f').map(str::to_string).collect()
    }
}

static ID: AtomicUsize = AtomicUsize::new(0);

fn probe<T: AsRef<[u8]>>(code: T) -> bool {
    let mut child = new_rustc_child();
    let mut stdin = child.stdin.take().expect("rustc stdin");

    stdin
        .write_all(b"#![no_std]\n#![allow(warnings)]\n")
        .unwrap();
    stdin.write_all(code.as_ref()).unwrap();
    drop(stdin);

    let status = child.wait().unwrap();
    status.success()
}

fn new_rustc_child() -> Child {
    let id = ID.fetch_add(1, Ordering::Relaxed);
    let mut command = Command::new(rustc_path());
    command
        .arg("--crate-name")
        .arg(format!("probe{}", id))
        .arg("--crate-type=lib")
        .arg("--out-dir")
        .arg(out_dir())
        .arg("--emit=llvm-ir");

    if let Some(target) = target() {
        command.arg("--target").arg(target);
    }

    command.args(rustflags());

    command.arg("-").stdin(Stdio::piped());
    command.stdout(Stdio::null()).stderr(Stdio::null());
    command.spawn().unwrap()
}