tcod-sys 5.0.1

Raw FFI bindings & build script to link against libtcod.
Documentation
extern crate cc;
extern crate pkg_config;

use std::env;
use std::fs;
use std::path::{Path, PathBuf};


fn build_libz(libz_sources: &[&str]) {
    let mut config = cc::Build::new();
    for c_file in libz_sources {
        config.file(c_file);
    }
    config.flag("-w");
    config.compile("libz.a");
}

fn build_libtcod_objects(mut config: cc::Build, sources: &[&str]) {
    config.include("libtcod/include");
    config.include("libtcod/src/zlib");
    for c_file in sources {
        config.file(c_file);
    }
    config.cargo_metadata(false);
    config.flag("-w");
    config.compile("libtcod.a");
}


fn compile_config(config: cc::Build) {
    let mut cmd = config.get_compiler().to_command();
    println!("Compiling: {:?}", cmd);
    match cmd.output() {
        Ok(output) => {
            println!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
            println!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
            if !output.status.success() {
                panic!("Compilation failed.");
            }
        }
        Err(e) => {
            panic!("Failed to run the compilation command {}.", e);
        }
    }
}


/// Build static libtcod for Linux
#[cfg(not(feature = "dynlib"))]
fn build_linux_static(_dst: &Path, libtcod_sources: &[& 'static str]) {
    // Tell rust to link the produced library
    // It is important to specify this first, so that the library will be linked
    // before SDL2, as the link order matters for static libraries
    println!("cargo:rustc-link-lib=static=tcod");
    let mut config = cc::Build::new();
    // Add dependencies
    for include_path in &pkg_config::find_library("sdl2").unwrap().include_paths {
        config.include(include_path);
    }
    // Build the library
    config.define("TCOD_SDL2", None);
    config.define("NO_OPENGL", None);
    config.define("NDEBUG", None);
    config.flag("-fno-strict-aliasing");
    config.flag("-ansi");
    build_libtcod_objects(config, libtcod_sources);
}


/// Build dynamic libtcod for Linux
#[cfg(feature = "dynlib")]
fn build_linux_dynamic(dst: &Path, libtcod_sources: &[& 'static str]) {
    // Build the *.o files:
    {
        let mut config = cc::Build::new();
        for include_path in &pkg_config::find_library("sdl2").unwrap().include_paths {
            config.include(include_path);
        }
        config.define("TCOD_SDL2", None);
        config.define("NO_OPENGL", None);
        config.define("NDEBUG", None);
        config.flag("-fno-strict-aliasing");
        config.flag("-ansi");
        build_libtcod_objects(config, libtcod_sources);
    }

    // Build the DLL
    let mut config = cc::Build::new();
    config.define("TCOD_SDL2", None);
    config.define("NO_OPENGL", None);
    config.define("NDEBUG", None);
    config.flag("-shared");
    config.flag("-Wl,-soname,libtcod.so");
    config.flag("-o");
    config.flag(dst.join("libtcod.so").to_str().unwrap());
    for c_file in libtcod_sources {
        config.flag(dst.join(c_file).with_extension("o").to_str().unwrap());
    }
    config.flag(dst.join("libz.a").to_str().unwrap());
    config.flag("-lSDL2");
    config.flag("-lX11");
    config.flag("-lm");
    config.flag("-ldl");
    config.flag("-lpthread");

    compile_config(config);
    assert!(dst.join("libtcod.so").is_file());

    pkg_config::find_library("x11").unwrap();
}


fn main() {
    let is_crater = option_env!("CRATER_TASK_TYPE");

    if is_crater.is_some() {
        return;
    }

    let src_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let dst_dir = env::var("OUT_DIR").unwrap();
    let target = env::var("TARGET").unwrap();

    let src = Path::new(&src_dir);
    let dst = Path::new(&dst_dir);
    let sdl_lib_dir = src.join("libtcod").join("dependencies").join("SDL2-2.0.7").join("lib").join(&target);
    let sdl_include_dir = src.join("libtcod").join("dependencies").join("SDL2-2.0.7").join("include");

    let libz_sources = &[
        "libtcod/src/zlib/adler32.c",
	    "libtcod/src/zlib/crc32.c",
	    "libtcod/src/zlib/deflate.c",
	    "libtcod/src/zlib/infback.c",
	    "libtcod/src/zlib/inffast.c",
	    "libtcod/src/zlib/inflate.c",
	    "libtcod/src/zlib/inftrees.c",
	    "libtcod/src/zlib/trees.c",
	    "libtcod/src/zlib/zutil.c",
	    "libtcod/src/zlib/compress.c",
	    "libtcod/src/zlib/uncompr.c",
	    "libtcod/src/zlib/gzclose.c",
	    "libtcod/src/zlib/gzlib.c",
	    "libtcod/src/zlib/gzread.c",
	    "libtcod/src/zlib/gzwrite.c",
    ];

    let libtcod_sources = &[
 	    "libtcod/src/bresenham_c.c",
	    "libtcod/src/bsp_c.c",
	    "libtcod/src/color_c.c",
	    "libtcod/src/console_c.c",
        "libtcod/src/console_rexpaint.c",
	    "libtcod/src/fov_c.c",
	    "libtcod/src/fov_circular_raycasting.c",
	    "libtcod/src/fov_diamond_raycasting.c",
	    "libtcod/src/fov_permissive2.c",
	    "libtcod/src/fov_recursive_shadowcasting.c",
	    "libtcod/src/fov_restrictive.c",
	    "libtcod/src/heightmap_c.c",
	    "libtcod/src/image_c.c",
	    "libtcod/src/lex_c.c",
	    "libtcod/src/list_c.c",
	    "libtcod/src/mersenne_c.c",
	    "libtcod/src/namegen_c.c",
	    "libtcod/src/noise_c.c",
	    "libtcod/src/parser_c.c",
	    "libtcod/src/path_c.c",
	    "libtcod/src/sys_c.c",
	    "libtcod/src/sys_sdl2_c.c",
	    "libtcod/src/sys_sdl_c.c",
	    "libtcod/src/sys_sdl_img_bmp.c",
	    "libtcod/src/sys_sdl_img_png.c",
	    "libtcod/src/tree_c.c",
	    "libtcod/src/txtfield_c.c",
	    "libtcod/src/wrappers.c",
	    "libtcod/src/zip_c.c",
	    "libtcod/src/png/lodepng.c",
    ];

    if target.contains("linux") {
        build_libz(libz_sources);

        #[cfg(not(feature = "dynlib"))]
        build_linux_static(&dst, libtcod_sources);
        #[cfg(feature = "dynlib")]
        build_linux_dynamic(&dst, libtcod_sources);

    } else if target.contains("darwin") {
        build_libz(libz_sources);

        // Build the *.o files
        {
            let mut config = cc::Build::new();
            for include_path in &pkg_config::find_library("sdl2").unwrap().include_paths {
                config.include(include_path);
            }
            config.define("TCOD_SDL2", None);
            config.define("NO_OPENGL", None);
            config.define("NDEBUG", None);
            config.flag("-fno-strict-aliasing");
            config.flag("-ansi");
            build_libtcod_objects(config, libtcod_sources);
        }

        // Build the DLL
        let mut config = cc::Build::new();
        config.define("TCOD_SDL2", None);
        config.define("NO_OPENGL", None);
        config.define("NDEBUG", None);
        config.flag("-shared");
        config.flag("-o");
        config.flag(dst.join("libtcod.dylib").to_str().unwrap());
        for c_file in libtcod_sources {
            config.flag(dst.join(c_file).with_extension("o").to_str().unwrap());
        }
        config.flag(dst.join("libz.a").to_str().unwrap());
        config.flag("-lSDL2");
        config.flag("-lSDL2main");
        config.flag("-framework");
        config.flag("Cocoa");
        config.flag("-lm");
        config.flag("-ldl");
        config.flag("-lpthread");

        compile_config(config);
        assert!(dst.join("libtcod.dylib").is_file());

        println!("cargo:rustc-link-lib=framework=Cocoa");

    } else if target.contains("windows-gnu") {
        assert!(sdl_lib_dir.is_dir());
        assert!(sdl_include_dir.is_dir());
        fs::copy(&sdl_lib_dir.join("SDL2.dll"), &dst.join("SDL2.dll")).unwrap();
        fs::copy(&sdl_lib_dir.join("libSDL2.a"), &dst.join("libSDL2.a")).unwrap();
        fs::copy(&sdl_lib_dir.join("libSDL2main.a"), &dst.join("libSDL2main.a")).unwrap();


        // Build the DLL
        let mut config = cc::Build::new();
        config.flag("-fno-strict-aliasing");
        config.flag("-ansi");
        config.define("TCOD_SDL2", None);
        config.define("NO_OPENGL", None);
        config.define("NDEBUG", None);
        config.define("LIBTCOD_EXPORTS", None);
        config.flag("-o");
        config.flag(dst.join("libtcod.dll").to_str().unwrap());
        config.flag("-shared");
        fs::create_dir(dst.join("lib")).unwrap();
        config.flag(&format!("-Wl,--out-implib,{}", dst.join("lib/libtcod.a").display()));
        config.include(Path::new("libtcod").join("src").join("zlib"));
        config.include(Path::new("libtcod").join("include"));
        for c_file in libz_sources.iter().chain(libtcod_sources) {
            let path = c_file.split('/').fold(PathBuf::new(), |path, segment| path.join(segment));
            config.flag(src.join(path).to_str().unwrap());
        }
        config.flag("-mwindows");
        config.flag("-L");
        config.flag(sdl_lib_dir.to_str().unwrap());
        config.flag("-lSDL2");
        config.flag("-lSDL2main");
        config.flag(&format!("-I{}", sdl_include_dir.to_str().unwrap()));
        config.flag("-static-libgcc");
        config.flag("-static-libstdc++");

        compile_config(config);
        assert!(dst.join("libtcod.dll").is_file());

        println!("cargo:rustc-link-lib=dylib={}", "SDL2");
        println!("cargo:rustc-link-search=native={}", sdl_lib_dir.display());
        println!("cargo:rustc-link-search=native={}", dst.display());

    } else if target.contains("windows-msvc") {
        assert!(sdl_lib_dir.is_dir());
        assert!(sdl_include_dir.is_dir());
        fs::copy(&sdl_lib_dir.join("SDL2.dll"), &dst.join("SDL2.dll")).unwrap();
        fs::copy(&sdl_lib_dir.join("SDL2.lib"), &dst.join("SDL2.lib")).unwrap();
        fs::copy(&sdl_lib_dir.join("SDL2main.lib"), &dst.join("SDL2main.lib")).unwrap();

        // Build the DLL
        let mut config = cc::Build::new();
        config.flag("/DTCOD_SDL2");
        config.flag("/DNO_OPENGL");
        config.flag("/DNDEBUG");
        config.flag("/DLIBTCOD_EXPORTS");
        config.flag(&format!("/Fo:{}\\", dst.to_str().unwrap()));
        config.include(sdl_include_dir.to_str().unwrap());
        config.include(Path::new("libtcod").join("src").join("zlib"));
        config.include(Path::new("libtcod").join("include"));
        for c_file in libz_sources.iter().chain(libtcod_sources) {
            // Make sure the path is in the Windows format. This
            // shouldn't matter but it's distracting when debugging
            // build script issues.
            let path = c_file.split('/').fold(PathBuf::new(), |path, segment| path.join(segment));
            config.flag(src.join(path).to_str().unwrap());
        }
        config.flag("User32.lib");
        config.flag("SDL2.lib");
        config.flag("SDL2main.lib");
        config.flag("/link");
        config.flag(&format!("/LIBPATH:{}", dst.to_str().unwrap()));
        config.flag("/DLL");
        config.flag(&format!("/OUT:{}", dst.join("tcod.dll").display()));

        compile_config(config);
        assert!(dst.join("tcod.dll").is_file());

        println!("cargo:rustc-link-search={}", dst.display());
        println!("cargo:rustc-link-lib=dylib=SDL2");
        println!("cargo:rustc-link-lib=User32");
    }

    println!("cargo:rustc-link-search={}", dst.display());
    println!("cargo:rustc-link-lib=dylib=tcod");
    println!("cargo:rustc-link-lib=SDL2");
}