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 msvcbuild.arg("lua52compat");
276 }
277 if self.debug.unwrap_or(cfg!(debug_assertions)) {
278 msvcbuild.arg("debug");
279 }
280 msvcbuild.arg("static");
281
282 let cl = cc::windows_registry::find_tool(target, "cl.exe").expect("failed to find cl");
283 for (k, v) in cl.env() {
284 msvcbuild.env(k, v);
285 }
286
287 self.run_command(&mut msvcbuild)
288 .context(|| format!("Error running'{msvcbuild:?}'"))?;
289
290 Artifacts::make(&build_dir, &include_dir, &lib_dir, true)
291 }
292
293 fn run_command(&self, command: &mut Command) -> io::Result<()> {
294 let status = command.status()?;
295 if !status.success() {
296 return Err(io::Error::other(format!("exited with status {status}")));
297 }
298 Ok(())
299 }
300}
301
302fn cp_r(src: &Path, dst: &Path) -> Result<(), DynError> {
303 for f in fs::read_dir(src).context(|| format!("Cannot read directory '{}'", src.display()))? {
304 let f = f.context(|| format!("Cannot read entry in '{}'", src.display()))?;
305 let path = f.path();
306 let name = path.file_name().unwrap();
307
308 if name.to_str() == Some(".git") {
310 continue;
311 }
312
313 let dst = dst.join(name);
314 if f.file_type().unwrap().is_dir() {
315 fs::create_dir_all(&dst)
316 .context(|| format!("Cannot create directory '{}'", dst.display()))?;
317 cp_r(&path, &dst)?;
318 } else {
319 let _ = fs::remove_file(&dst);
320 fs::copy(&path, &dst)
321 .context(|| format!("Cannot copy '{}' to '{}'", path.display(), dst.display()))?;
322 }
323 }
324 Ok(())
325}
326
327impl Artifacts {
328 pub fn include_dir(&self) -> &Path {
330 &self.include_dir
331 }
332
333 pub fn lib_dir(&self) -> &Path {
335 &self.lib_dir
336 }
337
338 pub fn libs(&self) -> &[String] {
340 &self.libs
341 }
342
343 pub fn print_cargo_metadata(&self) {
348 println!("cargo:rerun-if-env-changed=HOST_CC");
349 println!("cargo:rerun-if-env-changed=STATIC_CC");
350 println!("cargo:rerun-if-env-changed=TARGET_LD");
351 println!("cargo:rerun-if-env-changed=TARGET_AR");
352 println!("cargo:rerun-if-env-changed=TARGET_STRIP");
353 println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET");
354
355 println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
356 for lib in self.libs.iter() {
357 println!("cargo:rustc-link-lib=static={lib}");
358 }
359 }
360
361 fn make(
362 build_dir: &Path,
363 include_dir: &Path,
364 lib_dir: &Path,
365 is_msvc: bool,
366 ) -> Result<Self, DynError> {
367 for f in &["lauxlib.h", "lua.h", "luaconf.h", "luajit.h", "lualib.h"] {
368 let from = build_dir.join("src").join(f);
369 let to = include_dir.join(f);
370 fs::copy(&from, &to)
371 .context(|| format!("Cannot copy '{}' to '{}'", from.display(), to.display()))?;
372 }
373
374 let lib_name = if !is_msvc { "luajit" } else { "lua51" };
375 let lib_file = if !is_msvc { "libluajit.a" } else { "lua51.lib" };
376 if build_dir.join("src").join(lib_file).exists() {
377 let from = build_dir.join("src").join(lib_file);
378 let to = lib_dir.join(lib_file);
379 fs::copy(&from, &to)
380 .context(|| format!("Cannot copy '{}' to '{}'", from.display(), to.display()))?;
381 }
382
383 Ok(Artifacts {
384 lib_dir: lib_dir.to_path_buf(),
385 include_dir: include_dir.to_path_buf(),
386 libs: vec![lib_name.to_string()],
387 })
388 }
389}
390
391trait ErrorContext<T> {
392 fn context(self, f: impl FnOnce() -> String) -> Result<T, DynError>;
393}
394
395impl<T, E: Error> ErrorContext<T> for Result<T, E> {
396 fn context(self, f: impl FnOnce() -> String) -> Result<T, DynError> {
397 self.map_err(|e| format!("{}: {e}", f()).into())
398 }
399}