lua_src/
lib.rs

1use std::env;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, PartialEq, Eq)]
6pub enum Version {
7    Lua51,
8    Lua52,
9    Lua53,
10    Lua54,
11}
12pub use self::Version::*;
13
14pub struct Build {
15    out_dir: Option<PathBuf>,
16    target: Option<String>,
17}
18
19pub struct Artifacts {
20    lib_dir: PathBuf,
21    libs: Vec<String>,
22}
23
24impl Default for Build {
25    fn default() -> Build {
26        Build {
27            out_dir: env::var_os("OUT_DIR").map(PathBuf::from),
28            target: env::var("TARGET").ok(),
29        }
30    }
31}
32
33impl Build {
34    pub fn new() -> Build {
35        Build::default()
36    }
37
38    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
39        self.out_dir = Some(path.as_ref().to_path_buf());
40        self
41    }
42
43    pub fn target(&mut self, target: &str) -> &mut Build {
44        self.target = Some(target.to_string());
45        self
46    }
47
48    pub fn build(&mut self, version: Version) -> Artifacts {
49        let target = &self.target.as_ref().expect("TARGET is not set")[..];
50        let out_dir = self.out_dir.as_ref().expect("OUT_DIR is not set");
51        let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
52        let mut source_dir = manifest_dir.join(version.source_dir());
53        let build_dir = out_dir.join("lua-build");
54
55        let mut config = cc::Build::new();
56        config.warnings(false).cargo_metadata(false);
57
58        match target {
59            _ if target.contains("linux") => {
60                config.define("LUA_USE_LINUX", None);
61            }
62            _ if target.ends_with("bsd") => {
63                config.define("LUA_USE_LINUX", None);
64            }
65            _ if target.contains("apple-darwin") => {
66                match version {
67                    Lua51 => config.define("LUA_USE_LINUX", None),
68                    _ => config.define("LUA_USE_MACOSX", None),
69                };
70            }
71            _ if target.contains("apple-ios") => {
72                match version {
73                    Lua54 => config.define("LUA_USE_IOS", None),
74                    _ => config.define("LUA_USE_POSIX", None),
75                };
76            }
77            _ if target.contains("windows") => {
78                // Defined in Lua >= 5.3
79                config.define("LUA_USE_WINDOWS", None);
80            }
81            _ if target.ends_with("emscripten") => {
82                config
83                    .define("LUA_USE_POSIX", None)
84                    .cpp(true)
85                    .flag("-fexceptions"); // Enable exceptions to be caught
86
87                let cpp_source_dir = out_dir.join("cpp_source");
88                if cpp_source_dir.exists() {
89                    fs::remove_dir_all(&cpp_source_dir).unwrap();
90                }
91                fs::create_dir_all(&cpp_source_dir).unwrap();
92
93                for file in fs::read_dir(&source_dir).unwrap() {
94                    let file = file.unwrap();
95                    let filename = file.file_name();
96                    let filename = filename.to_str().unwrap();
97                    let src_file = source_dir.join(file.file_name());
98                    let dst_file = cpp_source_dir.join(file.file_name());
99
100                    let mut content = fs::read(src_file).unwrap();
101                    if ["lauxlib.h", "lua.h", "lualib.h"].contains(&filename) {
102                        content.splice(0..0, b"extern \"C\" {\n".to_vec());
103                        content.extend(b"\n}".to_vec())
104                    }
105                    fs::write(dst_file, content).unwrap();
106                }
107                source_dir = cpp_source_dir
108            }
109            _ => panic!("don't know how to build Lua for {target}"),
110        }
111
112        if let Lua54 = version {
113            config.define("LUA_COMPAT_5_3", None);
114            #[cfg(feature = "ucid")]
115            config.define("LUA_UCID", None);
116        }
117
118        if cfg!(debug_assertions) {
119            config.define("LUA_USE_APICHECK", None);
120        }
121
122        config
123            .include(&source_dir)
124            .flag("-w") // Suppress all warnings
125            .flag_if_supported("-fno-common") // Compile common globals like normal definitions
126            .add_files_by_ext(&source_dir, "c")
127            .out_dir(&build_dir)
128            .compile(version.lib_name());
129
130        Artifacts {
131            lib_dir: build_dir,
132            libs: vec![version.lib_name().to_string()],
133        }
134    }
135}
136
137impl Version {
138    fn source_dir(&self) -> &str {
139        match self {
140            Lua51 => "lua-5.1.5",
141            Lua52 => "lua-5.2.4",
142            Lua53 => "lua-5.3.6",
143            Lua54 => "lua-5.4.7",
144        }
145    }
146
147    fn lib_name(&self) -> &str {
148        match self {
149            Lua51 => "lua5.1",
150            Lua52 => "lua5.2",
151            Lua53 => "lua5.3",
152            Lua54 => "lua5.4",
153        }
154    }
155}
156
157impl Artifacts {
158    pub fn lib_dir(&self) -> &Path {
159        &self.lib_dir
160    }
161
162    pub fn libs(&self) -> &[String] {
163        &self.libs
164    }
165
166    pub fn print_cargo_metadata(&self) {
167        println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
168        for lib in self.libs.iter() {
169            println!("cargo:rustc-link-lib=static={lib}");
170        }
171    }
172}
173
174trait AddFilesByExt {
175    fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> &mut Self;
176}
177
178impl AddFilesByExt for cc::Build {
179    fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> &mut Self {
180        for entry in fs::read_dir(dir)
181            .unwrap()
182            .filter_map(|e| e.ok())
183            .filter(|e| e.path().extension() == Some(ext.as_ref()))
184        {
185            self.file(entry.path());
186        }
187        self
188    }
189}