luajit_src/
lib.rs

1use std::env;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6pub struct Build {
7    out_dir: Option<PathBuf>,
8    target: Option<String>,
9    host: Option<String>,
10    lua52compat: bool,
11}
12
13pub struct Artifacts {
14    lib_dir: PathBuf,
15    libs: Vec<String>,
16}
17
18impl Default for Build {
19    fn default() -> Self {
20        Build {
21            out_dir: env::var_os("OUT_DIR").map(PathBuf::from),
22            target: env::var("TARGET").ok(),
23            host: env::var("HOST").ok(),
24            lua52compat: false,
25        }
26    }
27}
28
29impl Build {
30    pub fn new() -> Build {
31        Build::default()
32    }
33
34    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
35        self.out_dir = Some(path.as_ref().to_path_buf());
36        self
37    }
38
39    pub fn target(&mut self, target: &str) -> &mut Build {
40        self.target = Some(target.to_string());
41        self
42    }
43
44    pub fn host(&mut self, host: &str) -> &mut Build {
45        self.host = Some(host.to_string());
46        self
47    }
48
49    pub fn lua52compat(&mut self, enabled: bool) -> &mut Build {
50        self.lua52compat = enabled;
51        self
52    }
53
54    fn cmd_make(&self) -> Command {
55        match &self.host.as_ref().expect("HOST is not set")[..] {
56            "x86_64-unknown-dragonfly" => Command::new("gmake"),
57            "x86_64-unknown-freebsd" => Command::new("gmake"),
58            _ => Command::new("make"),
59        }
60    }
61
62    pub fn build(&mut self) -> Artifacts {
63        let target = &self.target.as_ref().expect("TARGET is not set")[..];
64
65        if target.contains("msvc") {
66            return self.build_msvc();
67        }
68
69        self.build_unix()
70    }
71
72    fn build_unix(&mut self) -> Artifacts {
73        let target = &self.target.as_ref().expect("TARGET is not set")[..];
74        let host = &self.host.as_ref().expect("HOST is not set")[..];
75        let out_dir = self.out_dir.as_ref().expect("OUT_DIR is not set");
76        let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
77        let source_dir = manifest_dir.join("luajit2");
78        let build_dir = out_dir.join("luajit-build");
79
80        // Cleanup
81        if build_dir.exists() {
82            fs::remove_dir_all(&build_dir).unwrap();
83        }
84        fs::create_dir_all(&build_dir)
85            .unwrap_or_else(|e| panic!("cannot create {}: {}", build_dir.display(), e));
86        cp_r(&source_dir, &build_dir);
87
88        // Copy release version file
89        #[rustfmt::skip]
90        fs::copy(manifest_dir.join("luajit_relver.txt"), build_dir.join(".relver")).unwrap();
91
92        let mut cc = cc::Build::new();
93        cc.warnings(false);
94        let compiler = cc.get_compiler();
95        let compiler_path = compiler.path().to_str().unwrap();
96
97        let mut make = self.cmd_make();
98        make.current_dir(build_dir.join("src"));
99        make.arg("-e");
100
101        match target {
102            "x86_64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
103                make.env("MACOSX_DEPLOYMENT_TARGET", "10.11");
104            }
105            "aarch64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
106                make.env("MACOSX_DEPLOYMENT_TARGET", "11.0");
107            }
108            _ if target.contains("linux") => {
109                make.env("TARGET_SYS", "Linux");
110            }
111            _ if target.contains("windows") => {
112                make.env("TARGET_SYS", "Windows");
113            }
114            _ => {}
115        }
116
117        let target_pointer_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap();
118        if target_pointer_width == "32" && env::var_os("HOST_CC").is_none() {
119            // 32-bit cross-compilation?
120            let host_cc = cc::Build::new().target(host).get_compiler();
121            make.env("HOST_CC", format!("{} -m32", host_cc.path().display()));
122        }
123
124        // Infer ar/ranlib tools from cross compilers if the it looks like
125        // we're doing something like `foo-gcc` route that to `foo-ranlib`
126        // as well.
127        let prefix = if compiler_path.ends_with("-gcc") {
128            &compiler_path[..compiler_path.len() - 3]
129        } else if compiler_path.ends_with("-clang") {
130            &compiler_path[..compiler_path.len() - 5]
131        } else {
132            ""
133        };
134
135        let compiler_path =
136            which::which(compiler_path).unwrap_or_else(|_| panic!("cannot find {compiler_path}"));
137        let bindir = compiler_path.parent().unwrap();
138        let compiler_path = compiler_path.to_str().unwrap();
139        let compiler_args = compiler.cflags_env();
140        let compiler_args = compiler_args.to_str().unwrap();
141        if env::var_os("STATIC_CC").is_none() {
142            make.env("STATIC_CC", format!("{compiler_path} {compiler_args}"));
143        }
144        if env::var_os("TARGET_LD").is_none() {
145            make.env("TARGET_LD", format!("{compiler_path} {compiler_args}"));
146        }
147
148        // Find ar
149        if env::var_os("TARGET_AR").is_none() {
150            let mut ar = if bindir.join(format!("{prefix}ar")).is_file() {
151                bindir.join(format!("{prefix}ar")).into_os_string()
152            } else if compiler.is_like_clang() && bindir.join("llvm-ar").is_file() {
153                bindir.join("llvm-ar").into_os_string()
154            } else if compiler.is_like_gnu() && bindir.join("ar").is_file() {
155                bindir.join("ar").into_os_string()
156            } else if let Ok(ar) = which::which(format!("{prefix}ar")) {
157                ar.into_os_string()
158            } else {
159                panic!("cannot find {prefix}ar")
160            };
161            ar.push(" rcus");
162            make.env("TARGET_AR", ar);
163        }
164
165        // Find strip
166        if env::var_os("TARGET_STRIP").is_none() {
167            let strip = if bindir.join(format!("{prefix}strip")).is_file() {
168                bindir.join(format!("{prefix}strip"))
169            } else if compiler.is_like_clang() && bindir.join("llvm-strip").is_file() {
170                bindir.join("llvm-strip")
171            } else if compiler.is_like_gnu() && bindir.join("strip").is_file() {
172                bindir.join("strip")
173            } else if let Ok(strip) = which::which(format!("{prefix}strip")) {
174                strip
175            } else {
176                panic!("cannot find {prefix}strip")
177            };
178            make.env("TARGET_STRIP", strip);
179        }
180
181        let mut xcflags = vec!["-fPIC"];
182        if self.lua52compat {
183            xcflags.push("-DLUAJIT_ENABLE_LUA52COMPAT");
184        }
185        if cfg!(debug_assertions) {
186            xcflags.push("-DLUA_USE_ASSERT");
187            xcflags.push("-DLUA_USE_APICHECK");
188        }
189
190        make.env("BUILDMODE", "static");
191        make.env("XCFLAGS", xcflags.join(" "));
192        self.run_command(make, "building LuaJIT");
193
194        Artifacts {
195            lib_dir: build_dir.join("src"),
196            libs: vec!["luajit".to_string()],
197        }
198    }
199
200    fn build_msvc(&mut self) -> Artifacts {
201        let target = &self.target.as_ref().expect("TARGET is not set")[..];
202        let out_dir = self.out_dir.as_ref().expect("OUT_DIR is not set");
203        let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
204        let source_dir = manifest_dir.join("luajit2");
205        let extras_dir = manifest_dir.join("extras");
206        let build_dir = out_dir.join("luajit-build");
207
208        // Cleanup
209        if build_dir.exists() {
210            fs::remove_dir_all(&build_dir).unwrap();
211        }
212        fs::create_dir_all(&build_dir)
213            .unwrap_or_else(|e| panic!("cannot create {}: {}", build_dir.display(), e));
214        cp_r(&source_dir, &build_dir);
215
216        // Copy release version file
217        #[rustfmt::skip]
218        fs::copy(manifest_dir.join("luajit_relver.txt"), build_dir.join(".relver")).unwrap();
219
220        let mut msvcbuild = Command::new(build_dir.join("src").join("msvcbuild.bat"));
221        msvcbuild.current_dir(build_dir.join("src"));
222        if self.lua52compat {
223            cp_r(&extras_dir, &build_dir.join("src"));
224            msvcbuild.arg("lua52c");
225        }
226        msvcbuild.arg("static");
227
228        let cl = cc::windows_registry::find_tool(target, "cl.exe").expect("failed to find cl");
229        for (k, v) in cl.env() {
230            msvcbuild.env(k, v);
231        }
232
233        self.run_command(msvcbuild, "building LuaJIT");
234
235        Artifacts {
236            lib_dir: build_dir.join("src"),
237            libs: vec!["lua51".to_string()],
238        }
239    }
240
241    fn run_command(&self, mut command: Command, desc: &str) {
242        let status = command.status().unwrap();
243        if !status.success() {
244            panic!(
245                "
246Error {desc}:
247    Command: {command:?}
248    Exit status: {status}
249    "
250            );
251        }
252    }
253}
254
255fn cp_r(src: &Path, dst: &Path) {
256    for f in fs::read_dir(src).unwrap() {
257        let f = f.unwrap();
258        let path = f.path();
259        let name = path.file_name().unwrap();
260
261        // Skip git metadata
262        if name.to_str() == Some(".git") {
263            continue;
264        }
265
266        let dst = dst.join(name);
267        if f.file_type().unwrap().is_dir() {
268            fs::create_dir_all(&dst).unwrap();
269            cp_r(&path, &dst);
270        } else {
271            let _ = fs::remove_file(&dst);
272            fs::copy(&path, &dst).unwrap();
273        }
274    }
275}
276
277impl Artifacts {
278    pub fn lib_dir(&self) -> &Path {
279        &self.lib_dir
280    }
281
282    pub fn libs(&self) -> &[String] {
283        &self.libs
284    }
285
286    pub fn print_cargo_metadata(&self) {
287        println!("cargo:rerun-if-env-changed=HOST_CC");
288        println!("cargo:rerun-if-env-changed=STATIC_CC");
289        println!("cargo:rerun-if-env-changed=TARGET_LD");
290        println!("cargo:rerun-if-env-changed=TARGET_AR");
291        println!("cargo:rerun-if-env-changed=TARGET_STRIP");
292        println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET");
293
294        println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
295        for lib in self.libs.iter() {
296            println!("cargo:rustc-link-lib=static={lib}");
297        }
298    }
299}