lua 0.0.10

Bindings to Lua 5.3
Documentation
extern crate gcc;

use std::fs;
use std::io;
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::ffi::OsString;

trait CommandExt {
    fn execute(&mut self) -> io::Result<()>;
}

impl CommandExt for Command {
    /// Execute the command and return an error if it exited with a failure status.
    fn execute(&mut self) -> io::Result<()> {
        let status = try!(self.status());
        if status.success() {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other, format!("The command\n\
            \t{:?}\n\
            did not run successfully.", self)))
        }
    }
}

/// The command to build lua, with switches for different OSes.
fn build_lua(tooling: &gcc::Tool, source: &Path, build: &Path) -> io::Result<()> {
    // calculate the Lua platform name
    let platform = match env::var("TARGET").unwrap().split('-').nth(2).unwrap() {
        "windows" => "mingw",
        "macos" => "macosx",
        "linux" => "linux",
        "freebsd" => "freebsd",
        "dragonfly" => "bsd",
        // fall back to the "generic" system
        _ => "generic",
    };

    // extract CC and MYCFLAGS from the detected tooling
    let cc = tooling.path();
    let mut cflags = OsString::new();
    for arg in tooling.args() {
        cflags.push(arg);
        cflags.push(" ");
    }

    // VPATH is used to invoke "make" from the directory where we want Lua to
    // be built into, but read the sources from the provided source directory.
    // Setting MAKE to match the command we invoke means that the VPATH and
    // Makefile path will be carried over when the Makefile invokes itself.
    let makefile = source.join("Makefile");
    let make = OsString::from(format!("make -e -f {:?}", makefile.to_string_lossy().replace("\\", "/")));

    // call the makefile
    let mut command = Command::new("make");
    for &(ref key, ref val) in tooling.env() {
        command.env(key, val);
    }
    command.current_dir(build)
        .env("VPATH", source.to_string_lossy().replace("\\", "/"))
        .env("MAKE", make)
        .env("CC", cc)
        .env("MYCFLAGS", cflags)
        .arg("-e")
        .arg("-f").arg(makefile)
        .arg(platform)
        .execute()
}

/// If a static Lua is not yet available from a prior run of this script, this
/// will download Lua and build it. The cargo configuration text to link
/// statically against lua.a is then printed to stdout.
fn prebuild() -> io::Result<()> {
    let lua_dir = match env::var_os("LUA_LOCAL_SOURCE") {
        // If LUA_LOCAL_SOURCE is set, use it
        Some(dir) => PathBuf::from(dir),
        // Otherwise, pull from lua-source/src in the crate root
        None => {
            let mut dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
            dir.push("lua-source/src");
            dir
        }
    };
    let build_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let mut config = gcc::Config::new();

    println!("cargo:rustc-link-lib=static=lua");
    if lua_dir.join("liblua.a").exists() {
        // If liblua.a is already in lua_dir, use it
        println!("cargo:rustc-link-search=native={}", lua_dir.display());
    } else {
        // Otherwise, build from lua_dir into build_dir
        if !build_dir.join("liblua.a").exists() {
            let tooling = config.get_compiler();
            try!(fs::create_dir_all(&build_dir));
            try!(build_lua(&tooling, &lua_dir, &build_dir));
        }
        println!("cargo:rustc-link-search=native={}", build_dir.display());
    }

    // Ensure the presence of glue.rs
    if !build_dir.join("glue.rs").exists() {
        // Compile and run glue.c
        let glue = build_dir.join("glue");
        try!(config.include(&lua_dir).get_compiler().to_command()
            .arg("-I").arg(&lua_dir)
            .arg("src/glue/glue.c")
            .arg("-o").arg(&glue)
            .execute());
        try!(Command::new(glue)
            .arg(build_dir.join("glue.rs"))
            .execute());
    }

    Ok(())
}

fn main() {
    match prebuild() {
        Err(e) => panic!("Error: {}", e),
        Ok(()) => (),
    }
}