lua54-rs 0.1.0

Lua 5.4.1 bindings for Rust.
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;

use cc;

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

impl CommandExt for Command {
	fn execute(&mut self) -> io::Result<()> {
		let status = 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)))
		}
	}
}

fn build_lua(tooling: &cc::Tool, source: &Path, build: &Path) -> io::Result<()> {
	let platform = match env::var("TARGET").unwrap().split('-').nth(2).unwrap() {
		"windows" => "mingw",
		"darwin" => "macosx",
		"linux" => "linux",
		"freebsd" => "freebsd",
		"dragonfly" => "bsd",
		_ => "generic",
	};

	let cc = tooling.path();
	let mut cflags = OsString::new();
	for arg in tooling.args() {
		cflags.push(arg);
		cflags.push(" ");
	}

	let makefile = source.join("Makefile");
	let make = OsString::from(format!("make -e -f {:?}", makefile.to_string_lossy().replace("\\", "/")));

	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()
}

fn verify_msvc_environment() {
	let found_cl_exe = Command::new("cl.exe").arg("/help").output().is_ok();
	let found_lib_exe = Command::new("lib.exe").arg("/help").output().is_ok();

	if !found_cl_exe || !found_lib_exe {
		panic!("cl.exe and lib.exe must be on your %PATH% to compile Lua for MSVC.\n\
        Please install this crate through the Visual Studio Native Tools Command Line.");
	}
}

fn build_lua_msvc(source: &Path, build: &Path) -> io::Result<()> {
	verify_msvc_environment();
	let build_str = build.as_os_str().to_str().unwrap();
	let mut compile_cmd = Command::new("cl.exe");

	compile_cmd.current_dir(&source);

	for file_res in fs::read_dir(source).unwrap() {
		let dir_entry = file_res.unwrap();
		let file_name = dir_entry.file_name().into_string().unwrap();
		if file_name.ends_with(".c") && file_name != "luac.c" {
			compile_cmd.arg(file_name);
		}
	}

	compile_cmd.arg("/c")
		.arg("/MP")
		.arg(format!("/Fo{}\\", &build_str))
		.arg("/nologo");
	compile_cmd.execute().unwrap();
	let mut lib_cmd = Command::new("lib.exe");
	lib_cmd.current_dir(&build);
	for file_res in fs::read_dir(build).unwrap() {
		let dir_entry = file_res.unwrap();
		let file_name = dir_entry.file_name().into_string().unwrap();
		if file_name.ends_with(".obj") {
			lib_cmd.arg(file_name);
		}
	}
	lib_cmd.arg(format!("/out:{}\\lua.lib", &build_str))
		.arg("/NOLOGO");
	lib_cmd.execute()
}

fn prebuild() -> io::Result<()> {
	let lua_dir: PathBuf = match env::var_os("LUA_LOCAL_SOURCE") {
		Some(dir) => PathBuf::from(dir),
		None => {
			let mut dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
			dir.push(OsStr::new("lua/src").to_str().unwrap());
			dir
		}
	};
	let build_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
	let mut config = cc::Build::new();
	let msvc = env::var("TARGET").unwrap().split('-').last().unwrap() == "msvc";
	println!("cargo:rustc-link-lib=static=lua");
	if !msvc && lua_dir.join("liblua.a").exists() {
		println!("cargo:rustc-link-search=native={}", &lua_dir.display());
	} else if msvc {
		if !build_dir.join("lua.lib").exists() {
			build_lua_msvc(&lua_dir, &build_dir)?;
		}
		println!("cargo:rustc-link-search=native={}", &build_dir.display());
	} else {
		if !build_dir.join("liblua.a").exists() {
			let tooling = config.get_compiler();
			fs::create_dir_all(&build_dir)?;
			build_lua(&tooling, &lua_dir, &build_dir)?;
		}
		println!("cargo:rustc-link-search=native={}", &build_dir.display());
	}

	if !build_dir.join("glue.rs").exists() {
		let glue = build_dir.join("glue");
		config.include(&lua_dir).get_compiler().to_command()
			.arg("-I").arg(&lua_dir)
			.arg("src/glue/glue.c")
			.arg("-o").arg(&glue)
			.execute()?;
		Command::new(glue)
			.arg(build_dir.join("glue.rs"))
			.execute()?;
	}
	Ok(())
}

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