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