use std::env;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, PartialEq, Eq)]
pub enum Version {
Lua51,
Lua52,
Lua53,
Lua54,
Lua55,
}
pub use self::Version::*;
pub struct Build {
out_dir: Option<PathBuf>,
target: Option<String>,
host: Option<String>,
opt_level: Option<String>,
debug: Option<bool>,
}
#[derive(Clone, Debug)]
pub struct Artifacts {
include_dir: PathBuf,
lib_dir: PathBuf,
libs: Vec<String>,
}
impl Default for Build {
fn default() -> Build {
Build {
out_dir: env::var_os("OUT_DIR").map(PathBuf::from),
target: env::var("TARGET").ok(),
host: None,
opt_level: None,
debug: None,
}
}
}
impl Build {
pub fn new() -> Build {
Build::default()
}
pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
self.out_dir = Some(path.as_ref().to_path_buf());
self
}
pub fn target(&mut self, target: &str) -> &mut Build {
self.target = Some(target.to_string());
self
}
pub fn host(&mut self, host: &str) -> &mut Build {
self.host = Some(host.to_string());
self
}
pub fn opt_level(&mut self, opt_level: &str) -> &mut Build {
self.opt_level = Some(opt_level.to_string());
self
}
pub fn debug(&mut self, debug: bool) -> &mut Build {
self.debug = Some(debug);
self
}
pub fn build(&self, version: Version) -> Artifacts {
match self.try_build(version) {
Ok(artifacts) => artifacts,
Err(err) => panic!("{err}"),
}
}
pub fn try_build(&self, version: Version) -> Result<Artifacts, Box<dyn Error>> {
let target = self.target.as_ref().ok_or("TARGET is not set")?;
let out_dir = self.out_dir.as_ref().ok_or("OUT_DIR is not set")?;
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let source_dir = manifest_dir.join(version.source_dir());
let lib_dir = out_dir.join("lib");
let include_dir = out_dir.join("include");
if !include_dir.exists() {
fs::create_dir_all(&include_dir)
.context(|| format!("Cannot create '{}'", include_dir.display()))?;
}
let mut config = cc::Build::new();
config.warnings(false).cargo_metadata(false).target(target);
match &self.host {
Some(host) => {
config.host(host);
}
None if env::var("HOST").is_ok() => {}
None => {
config.host(target);
}
}
let mut libs = vec![version.lib_name().to_string()];
match target {
_ if target.contains("linux") => {
config.define("LUA_USE_LINUX", None);
}
_ if target.ends_with("bsd") => {
config.define("LUA_USE_LINUX", None);
}
_ if target.ends_with("illumos") => {
config.define("LUA_USE_POSIX", None);
}
_ if target.ends_with("solaris") => {
config.define("LUA_USE_POSIX", None);
}
_ if target.contains("apple-darwin") => {
match version {
Lua51 => config.define("LUA_USE_LINUX", None),
_ => config.define("LUA_USE_MACOSX", None),
};
}
_ if target.contains("apple-ios") => {
match version {
Lua54 | Lua55 => config.define("LUA_USE_IOS", None),
_ => config.define("LUA_USE_POSIX", None),
};
}
_ if target.contains("windows") => {
config.define("LUA_USE_WINDOWS", None);
}
_ if target.ends_with("emscripten") => {
config
.define("LUA_USE_POSIX", None)
.flag("-sSUPPORT_LONGJMP=wasm"); }
_ if target.contains("wasi") => {
config.define("LUA_USE_POSIX", None);
config.define("_WASI_EMULATED_SIGNAL", None);
libs.push("wasi-emulated-signal".to_string());
config.flag("-mllvm").flag("-wasm-enable-eh");
config.flag("-mllvm").flag("-wasm-use-legacy-eh=false");
config.flag("-mllvm").flag("-wasm-enable-sjlj");
libs.push("setjmp".to_string());
}
_ => Err(format!("don't know how to build Lua for {target}"))?,
}
if let Lua54 = version {
config.define("LUA_COMPAT_5_3", None);
}
#[cfg(feature = "ucid")]
if let Lua54 | Lua55 = version {
config.define("LUA_UCID", None);
}
let debug = self.debug.unwrap_or(cfg!(debug_assertions));
if debug {
config.define("LUA_USE_APICHECK", None);
config.debug(true);
}
match &self.opt_level {
Some(opt_level) => {
config.opt_level_str(opt_level);
}
None if env::var("OPT_LEVEL").is_ok() => {}
None => {
config.opt_level(if debug { 0 } else { 2 });
}
}
config
.include(&source_dir)
.warnings(false) .flag_if_supported("-fno-common") .add_files_by_ext(&source_dir, "c")?
.out_dir(&lib_dir)
.try_compile(version.lib_name())?;
for f in &["lauxlib.h", "lua.h", "luaconf.h", "lualib.h"] {
let from = source_dir.join(f);
let to = include_dir.join(f);
fs::copy(&from, &to)
.context(|| format!("Cannot copy '{}' to '{}'", from.display(), to.display()))?;
}
Ok(Artifacts {
include_dir,
lib_dir,
libs,
})
}
}
impl Version {
fn source_dir(&self) -> &'static str {
match self {
Lua51 => "lua-5.1.5",
Lua52 => "lua-5.2.4",
Lua53 => "lua-5.3.6",
Lua54 => "lua-5.4.8",
Lua55 => "lua-5.5.0",
}
}
fn lib_name(&self) -> &'static str {
match self {
Lua51 => "lua5.1",
Lua52 => "lua5.2",
Lua53 => "lua5.3",
Lua54 => "lua5.4",
Lua55 => "lua5.5",
}
}
}
impl Artifacts {
pub fn include_dir(&self) -> &Path {
&self.include_dir
}
pub fn lib_dir(&self) -> &Path {
&self.lib_dir
}
pub fn libs(&self) -> &[String] {
&self.libs
}
pub fn print_cargo_metadata(&self) {
println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
for lib in self.libs.iter() {
println!("cargo:rustc-link-lib=static:-bundle={lib}");
}
println!("cargo:include={}", self.include_dir.display());
println!("cargo:lib={}", self.lib_dir.display());
}
}
trait ErrorContext<T> {
fn context(self, f: impl FnOnce() -> String) -> Result<T, Box<dyn Error>>;
}
impl<T, E: Error> ErrorContext<T> for Result<T, E> {
fn context(self, f: impl FnOnce() -> String) -> Result<T, Box<dyn Error>> {
self.map_err(|e| format!("{}: {e}", f()).into())
}
}
trait AddFilesByExt {
fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> Result<&mut Self, Box<dyn Error>>;
}
impl AddFilesByExt for cc::Build {
fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> Result<&mut Self, Box<dyn Error>> {
for entry in fs::read_dir(dir)
.context(|| format!("Cannot read '{}'", dir.display()))?
.filter_map(|e| e.ok())
.filter(|e| e.path().extension() == Some(ext.as_ref()))
{
self.file(entry.path());
}
Ok(self)
}
}