1use std::error::Error;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4use std::{env, fs, io};
5
6type DynError = Box<dyn Error + Send + Sync>;
7
8pub struct Build {
10 out_dir: Option<PathBuf>,
11 target: Option<String>,
12 host: Option<String>,
13 lua52compat: bool,
14 debug: Option<bool>,
15}
16
17pub struct Artifacts {
19 include_dir: PathBuf,
20 lib_dir: PathBuf,
21 libs: Vec<String>,
22}
23
24impl Default for Build {
25 fn default() -> Self {
26 Build {
27 out_dir: env::var_os("OUT_DIR").map(PathBuf::from),
28 target: env::var("TARGET").ok(),
29 host: env::var("HOST").ok(),
30 lua52compat: false,
31 debug: None,
32 }
33 }
34}
35
36impl Build {
37 pub fn new() -> Build {
39 Build::default()
40 }
41
42 pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
46 self.out_dir = Some(path.as_ref().to_path_buf());
47 self
48 }
49
50 pub fn target(&mut self, target: &str) -> &mut Build {
54 self.target = Some(target.to_string());
55 self
56 }
57
58 pub fn host(&mut self, host: &str) -> &mut Build {
63 self.host = Some(host.to_string());
64 self
65 }
66
67 pub fn lua52compat(&mut self, enabled: bool) -> &mut Build {
69 self.lua52compat = enabled;
70 self
71 }
72
73 pub fn debug(&mut self, debug: bool) -> &mut Build {
78 self.debug = Some(debug);
79 self
80 }
81
82 fn cmd_make(&self) -> Command {
83 match &self.host.as_ref().expect("HOST is not set")[..] {
84 "x86_64-unknown-dragonfly" => Command::new("gmake"),
85 "x86_64-unknown-freebsd" => Command::new("gmake"),
86 _ => Command::new("make"),
87 }
88 }
89
90 pub fn build(&mut self) -> Artifacts {
92 self.try_build().expect("LuaJIT build failed")
93 }
94
95 pub fn try_build(&mut self) -> Result<Artifacts, DynError> {
99 let target = &self.target.as_ref().expect("TARGET is not set")[..];
100
101 if target.contains("msvc") {
102 return self.build_msvc();
103 }
104
105 self.build_unix()
106 }
107
108 fn build_unix(&mut self) -> Result<Artifacts, DynError> {
109 let target = &self.target.as_ref().expect("TARGET is not set")[..];
110 let host = &self.host.as_ref().expect("HOST is not set")[..];
111 let out_dir = self.out_dir.as_ref().expect("OUT_DIR is not set");
112 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
113 let source_dir = manifest_dir.join("luajit2");
114 let build_dir = out_dir.join("luajit-build");
115 let lib_dir = out_dir.join("lib");
116 let include_dir = out_dir.join("include");
117
118 for dir in [&build_dir, &lib_dir, &include_dir] {
120 if dir.exists() {
121 fs::remove_dir_all(dir).context(|| format!("Cannot remove {}", dir.display()))?;
122 }
123 fs::create_dir_all(dir).context(|| format!("Cannot create {}", dir.display()))?;
124 }
125 cp_r(&source_dir, &build_dir)?;
126
127 let relver = build_dir.join(".relver");
129 fs::copy(manifest_dir.join("luajit_relver.txt"), &relver)
130 .context(|| format!("Cannot copy 'luajit_relver.txt'"))?;
131
132 let mut perms = (fs::metadata(&relver).map(|md| md.permissions()))
134 .context(|| format!("Cannot read permissions for '{}'", relver.display()))?;
135 perms.set_readonly(false);
136 fs::set_permissions(&relver, perms)
137 .context(|| format!("Cannot set permissions for '{}'", relver.display()))?;
138
139 let mut cc = cc::Build::new();
140 cc.warnings(false);
141 let compiler = cc.get_compiler();
142 let compiler_path = compiler.path().to_str().unwrap();
143
144 let mut make = self.cmd_make();
145 make.current_dir(build_dir.join("src"));
146 make.arg("-e");
147
148 match target {
149 "x86_64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
150 make.env("MACOSX_DEPLOYMENT_TARGET", "10.14");
151 }
152 "aarch64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
153 make.env("MACOSX_DEPLOYMENT_TARGET", "11.0");
154 }
155 _ if target.contains("linux") => {
156 make.env("TARGET_SYS", "Linux");
157 }
158 _ if target.contains("windows") => {
159 make.env("TARGET_SYS", "Windows");
160 }
161 _ => {}
162 }
163
164 let target_pointer_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap();
165 if target_pointer_width == "32" && env::var_os("HOST_CC").is_none() {
166 let host_cc = cc::Build::new().target(host).get_compiler();
168 make.env("HOST_CC", format!("{} -m32", host_cc.path().display()));
169 }
170
171 let prefix = if compiler_path.ends_with("-gcc") {
175 &compiler_path[..compiler_path.len() - 3]
176 } else if compiler_path.ends_with("-clang") {
177 &compiler_path[..compiler_path.len() - 5]
178 } else {
179 ""
180 };
181
182 let compiler_path =
183 which::which(compiler_path).context(|| format!("Cannot find {compiler_path}"))?;
184 let bindir = compiler_path.parent().unwrap();
185 let compiler_path = compiler_path.to_str().unwrap();
186 let compiler_args = compiler.cflags_env();
187 let compiler_args = compiler_args.to_str().unwrap();
188 if env::var_os("STATIC_CC").is_none() {
189 make.env("STATIC_CC", format!("{compiler_path} {compiler_args}"));
190 }
191 if env::var_os("TARGET_LD").is_none() {
192 make.env("TARGET_LD", format!("{compiler_path} {compiler_args}"));
193 }
194
195 if env::var_os("TARGET_AR").is_none() {
197 let mut ar = if bindir.join(format!("{prefix}ar")).is_file() {
198 bindir.join(format!("{prefix}ar")).into_os_string()
199 } else if compiler.is_like_clang() && bindir.join("llvm-ar").is_file() {
200 bindir.join("llvm-ar").into_os_string()
201 } else if compiler.is_like_gnu() && bindir.join("ar").is_file() {
202 bindir.join("ar").into_os_string()
203 } else if let Ok(ar) = which::which(format!("{prefix}ar")) {
204 ar.into_os_string()
205 } else {
206 panic!("cannot find {prefix}ar")
207 };
208 ar.push(" rcus");
209 make.env("TARGET_AR", ar);
210 }
211
212 if env::var_os("TARGET_STRIP").is_none() {
214 let strip = if bindir.join(format!("{prefix}strip")).is_file() {
215 bindir.join(format!("{prefix}strip"))
216 } else if compiler.is_like_clang() && bindir.join("llvm-strip").is_file() {
217 bindir.join("llvm-strip")
218 } else if compiler.is_like_gnu() && bindir.join("strip").is_file() {
219 bindir.join("strip")
220 } else if let Ok(strip) = which::which(format!("{prefix}strip")) {
221 strip
222 } else {
223 panic!("cannot find {prefix}strip")
224 };
225 make.env("TARGET_STRIP", strip);
226 }
227
228 let mut xcflags = vec!["-fPIC"];
229 if self.lua52compat {
230 xcflags.push("-DLUAJIT_ENABLE_LUA52COMPAT");
231 }
232
233 let debug = self.debug.unwrap_or(cfg!(debug_assertions));
234 if debug {
235 make.env("CCDEBUG", "-g");
236 xcflags.push("-DLUA_USE_ASSERT");
237 xcflags.push("-DLUA_USE_APICHECK");
238 }
239
240 make.env("BUILDMODE", "static");
241 make.env("XCFLAGS", xcflags.join(" "));
242 self.run_command(&mut make)
243 .context(|| format!("Error running '{make:?}'"))?;
244
245 Artifacts::make(&build_dir, &include_dir, &lib_dir, false)
246 }
247
248 fn build_msvc(&mut self) -> Result<Artifacts, DynError> {
249 let target = &self.target.as_ref().expect("TARGET is not set")[..];
250 let out_dir = self.out_dir.as_ref().expect("OUT_DIR is not set");
251 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
252 let source_dir = manifest_dir.join("luajit2");
253 let build_dir = out_dir.join("luajit-build");
254 let lib_dir = out_dir.join("lib");
255 let include_dir = out_dir.join("include");
256
257 for dir in [&build_dir, &lib_dir, &include_dir] {
259 if dir.exists() {
260 fs::remove_dir_all(dir).context(|| format!("Cannot remove {}", dir.display()))?;
261 }
262 fs::create_dir_all(dir).context(|| format!("Cannot create {}", dir.display()))?;
263 }
264 cp_r(&source_dir, &build_dir)?;
265
266 let relver = build_dir.join(".relver");
268 fs::copy(manifest_dir.join("luajit_relver.txt"), &relver)
269 .context(|| format!("Cannot copy 'luajit_relver.txt'"))?;
270
271 let mut msvcbuild = Command::new(build_dir.join("src").join("msvcbuild.bat"));
272 msvcbuild.current_dir(build_dir.join("src"));
273
274 if self.lua52compat {
275 cp_r(&manifest_dir.join("extras"), &build_dir.join("src"))?;
276 msvcbuild.arg("lua52compat");
277 }
278 if self.debug.unwrap_or(cfg!(debug_assertions)) {
279 msvcbuild.arg("debug");
280 }
281 msvcbuild.arg("static");
282
283 let cl = cc::windows_registry::find_tool(target, "cl.exe").expect("failed to find cl");
284 for (k, v) in cl.env() {
285 msvcbuild.env(k, v);
286 }
287
288 self.run_command(&mut msvcbuild)
289 .context(|| format!("Error running'{msvcbuild:?}'"))?;
290
291 Artifacts::make(&build_dir, &include_dir, &lib_dir, true)
292 }
293
294 fn run_command(&self, command: &mut Command) -> io::Result<()> {
295 let status = command.status()?;
296 if !status.success() {
297 return Err(io::Error::other(format!("exited with status {status}")));
298 }
299 Ok(())
300 }
301}
302
303fn cp_r(src: &Path, dst: &Path) -> Result<(), DynError> {
304 for f in fs::read_dir(src).context(|| format!("Cannot read directory '{}'", src.display()))? {
305 let f = f.context(|| format!("Cannot read entry in '{}'", src.display()))?;
306 let path = f.path();
307 let name = path.file_name().unwrap();
308
309 if name.to_str() == Some(".git") {
311 continue;
312 }
313
314 let dst = dst.join(name);
315 if f.file_type().unwrap().is_dir() {
316 fs::create_dir_all(&dst)
317 .context(|| format!("Cannot create directory '{}'", dst.display()))?;
318 cp_r(&path, &dst)?;
319 } else {
320 let _ = fs::remove_file(&dst);
321 fs::copy(&path, &dst)
322 .context(|| format!("Cannot copy '{}' to '{}'", path.display(), dst.display()))?;
323 }
324 }
325 Ok(())
326}
327
328impl Artifacts {
329 pub fn include_dir(&self) -> &Path {
331 &self.include_dir
332 }
333
334 pub fn lib_dir(&self) -> &Path {
336 &self.lib_dir
337 }
338
339 pub fn libs(&self) -> &[String] {
341 &self.libs
342 }
343
344 pub fn print_cargo_metadata(&self) {
349 println!("cargo:rerun-if-env-changed=HOST_CC");
350 println!("cargo:rerun-if-env-changed=STATIC_CC");
351 println!("cargo:rerun-if-env-changed=TARGET_LD");
352 println!("cargo:rerun-if-env-changed=TARGET_AR");
353 println!("cargo:rerun-if-env-changed=TARGET_STRIP");
354 println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET");
355
356 println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
357 for lib in self.libs.iter() {
358 println!("cargo:rustc-link-lib=static={lib}");
359 }
360 }
361
362 fn make(
363 build_dir: &Path,
364 include_dir: &Path,
365 lib_dir: &Path,
366 is_msvc: bool,
367 ) -> Result<Self, DynError> {
368 for f in &["lauxlib.h", "lua.h", "luaconf.h", "luajit.h", "lualib.h"] {
369 let from = build_dir.join("src").join(f);
370 let to = include_dir.join(f);
371 fs::copy(&from, &to)
372 .context(|| format!("Cannot copy '{}' to '{}'", from.display(), to.display()))?;
373 }
374
375 let lib_name = if !is_msvc { "luajit" } else { "lua51" };
376 let lib_file = if !is_msvc { "libluajit.a" } else { "lua51.lib" };
377 if build_dir.join("src").join(lib_file).exists() {
378 let from = build_dir.join("src").join(lib_file);
379 let to = lib_dir.join(lib_file);
380 fs::copy(&from, &to)
381 .context(|| format!("Cannot copy '{}' to '{}'", from.display(), to.display()))?;
382 }
383
384 Ok(Artifacts {
385 lib_dir: lib_dir.to_path_buf(),
386 include_dir: include_dir.to_path_buf(),
387 libs: vec![lib_name.to_string()],
388 })
389 }
390}
391
392trait ErrorContext<T> {
393 fn context(self, f: impl FnOnce() -> String) -> Result<T, DynError>;
394}
395
396impl<T, E: Error> ErrorContext<T> for Result<T, E> {
397 fn context(self, f: impl FnOnce() -> String) -> Result<T, DynError> {
398 self.map_err(|e| format!("{}: {e}", f()).into())
399 }
400}