extern crate cc;

use std::env;
use std::fs::File;
use std::path::PathBuf;

fn main() {
    let target = env::var("TARGET").unwrap();

    if !cfg!(feature = "backtrace-sys") || // without this feature, this crate does nothing
        target.contains("msvc") || // libbacktrace isn't used on MSVC windows
        target.contains("emscripten") || // no way this will ever compile for emscripten
        target.contains("cloudabi") ||
        target.contains("hermit") ||
        target.contains("wasm32") ||
        target.contains("fuchsia") ||
        target.contains("uclibc")
    {
        println!("cargo:rustc-cfg=empty");
        return;
    }

    let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());

    let mut build = cc::Build::new();
    build
        .include("src/libbacktrace")
        .include(&out_dir)
        .warnings(false)
        .file("src/libbacktrace/alloc.c")
        .file("src/libbacktrace/dwarf.c")
        .file("src/libbacktrace/fileline.c")
        .file("src/libbacktrace/posix.c")
        .file("src/libbacktrace/sort.c")
        .file("src/libbacktrace/state.c");

    // `mmap` does not exist on Windows, so we use
    // the less efficient `read`-based code.
    // Using `mmap` on macOS causes weird isseus - see
    // https://github.com/rust-lang/rust/pull/45866
    if target.contains("windows") || target.contains("darwin") {
        build.file("src/libbacktrace/read.c");
    } else {
        build.file("src/libbacktrace/mmapio.c");
    }

    // No need to have any symbols reexported form shared objects
    build.flag("-fvisibility=hidden");

    if target.contains("darwin") {
        build.file("src/libbacktrace/macho.c");
        build.define("HAVE_MACH_O_DYLD_H", "1");
    } else if target.contains("windows") {
        build.file("src/libbacktrace/pecoff.c");
    } else {
        build.file("src/libbacktrace/elf.c");

        let pointer_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap();
        if pointer_width == "64" {
            build.define("BACKTRACE_ELF_SIZE", "64");
        } else {
            build.define("BACKTRACE_ELF_SIZE", "32");
        }
    }

    File::create(out_dir.join("backtrace-supported.h")).unwrap();
    build.define("BACKTRACE_SUPPORTED", "1");
    build.define("BACKTRACE_USES_MALLOC", "1");
    build.define("BACKTRACE_SUPPORTS_THREADS", "0");
    build.define("BACKTRACE_SUPPORTS_DATA", "0");

    File::create(out_dir.join("config.h")).unwrap();
    if target.contains("android") {
        maybe_enable_dl_iterate_phdr_android(&mut build);
    } else if target.contains("dragonfly") || target.contains("freebsd") {
        build.define("HAVE_DL_ITERATE_PHDR", "1");
        build.define("HAVE_KERN_PROC", "1");
    } else if target.contains("netbsd") {
        build.define("HAVE_DL_ITERATE_PHDR", "1");
        build.define("HAVE_KERN_PROC_ARGS", "1");
    } else if !target.contains("apple-ios")
        && !target.contains("solaris")
        && !target.contains("redox")
        && !target.contains("haiku")
        && !target.contains("vxworks")
    {
        build.define("HAVE_DL_ITERATE_PHDR", "1");
    }
    build.define("_GNU_SOURCE", "1");
    build.define("_LARGE_FILES", "1");

    // When we're built as part of the Rust compiler, this is used to enable
    // debug information in libbacktrace itself.
    let any_debug = env::var("RUSTC_DEBUGINFO").unwrap_or_default() == "true"
        || env::var("RUSTC_DEBUGINFO_LINES").unwrap_or_default() == "true";
    build.debug(any_debug);

    let syms = [
        "backtrace_full",
        "backtrace_dwarf_add",
        "backtrace_initialize",
        "backtrace_pcinfo",
        "backtrace_syminfo",
        "backtrace_get_view",
        "backtrace_release_view",
        "backtrace_alloc",
        "backtrace_free",
        "backtrace_vector_finish",
        "backtrace_vector_grow",
        "backtrace_vector_release",
        "backtrace_close",
        "backtrace_open",
        "backtrace_print",
        "backtrace_simple",
        "backtrace_qsort",
        "backtrace_create_state",
        "backtrace_uncompress_zdebug",
        // These should be `static` in C, but they aren't...
        "macho_get_view",
        "macho_symbol_type_relevant",
        "macho_get_commands",
        "macho_try_dsym",
        "macho_try_dwarf",
        "macho_get_addr_range",
        "macho_get_uuid",
        "macho_add",
        "macho_add_symtab",
        "macho_file_to_host_u64",
        "macho_file_to_host_u32",
        "macho_file_to_host_u16",
    ];
    let prefix = if cfg!(feature = "rustc-dep-of-std") {
        println!("cargo:rustc-cfg=rdos");
        "__rdos_"
    } else {
        println!("cargo:rustc-cfg=rbt");
        "__rbt_"
    };
    for sym in syms.iter() {
        build.define(sym, &format!("{}{}", prefix, sym)[..]);
    }

    build.compile("backtrace");
}

// The `dl_iterate_phdr` API was added in Android API 21+ (according to #227),
// so if we can dynamically detect an appropriately configured C compiler for
// that then let's enable the `dl_iterate_phdr` API, otherwise Android just
// won't have any information.
fn maybe_enable_dl_iterate_phdr_android(build: &mut cc::Build) {
    let expansion = cc::Build::new().file("src/android-api.c").expand();
    let expansion = match std::str::from_utf8(&expansion) {
        Ok(s) => s,
        Err(_) => return,
    };
    println!("expanded android version detection:\n{}", expansion);
    let marker = "APIVERSION";
    let i = match expansion.find(marker) {
        Some(i) => i,
        None => return,
    };
    let version = match expansion[i + marker.len() + 1..].split_whitespace().next() {
        Some(s) => s,
        None => return,
    };
    let version = match version.parse::<u32>() {
        Ok(n) => n,
        Err(_) => return,
    };
    if version >= 21 {
        build.define("HAVE_DL_ITERATE_PHDR", "1");
    }
}
