1use std::{
2 env,
3 io::{self, Cursor},
4 path::Path,
5 process::{ExitStatus, Stdio},
6};
7
8use crate::{
9 build::utils,
10 config::Config,
11 hash::HasIntegrity,
12 lua_version::LuaVersion,
13 operations::{self, UnpackError},
14 progress::{Progress, ProgressBar},
15};
16use bon::Builder;
17use git2::{build::RepoBuilder, FetchOptions};
18use miette::Diagnostic;
19use path_slash::PathExt;
20use ssri::Integrity;
21use target_lexicon::Triple;
22use tempfile::tempdir;
23use thiserror::Error;
24use tokio::{fs, process::Command};
25use url::Url;
26
27const LUA51_VERSION: &str = "5.1.5";
28const LUA51_HASH: &str = "sha256-JkD8VqeV8p0o7xXhPDSkfiI5YLAkDoywqC2bBzhpUzM=";
29const LUA52_VERSION: &str = "5.2.4";
30const LUA52_HASH: &str = "sha256-ueLkqtZ4mztjoFbUQveznw7Pyjrg8fwK5OlhRAG2n0s=";
31const LUA53_VERSION: &str = "5.3.6";
32const LUA53_HASH: &str = "sha256-/F/Wm7hzYyPwJmcrG3I12mE9cXfnJViJOgvc0yBGbWA=";
33const LUA54_VERSION: &str = "5.4.8";
34const LUA54_HASH: &str = "sha256-TxjdrhVOeT5G7qtyfFnvHAwMK3ROe5QhlxDXb1MGKa4=";
35const LUA55_VERSION: &str = "5.5.0";
36const LUA55_HASH: &str = "sha256-V8zDK7vQBcq3W8xSREBSU1r2kXiduiuQFtXFBkDWiz0=";
37const LUAJIT_MM_VERSION: &str = "2.1";
40
41#[derive(Builder)]
42#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
43pub struct BuildLua<'a> {
44 lua_version: &'a LuaVersion,
45 install_dir: &'a Path,
46 config: &'a Config,
47 progress: &'a Progress<ProgressBar>,
48}
49
50#[derive(Debug, Error, Diagnostic)]
51pub enum BuildLuaError {
52 #[error(transparent)]
53 Request(#[from] reqwest::Error),
54 #[error(transparent)]
55 Io(#[from] io::Error),
56 #[error(transparent)]
57 #[diagnostic(transparent)]
58 Unpack(#[from] UnpackError),
59 #[error(transparent)]
60 Git(#[from] git2::Error),
61 #[error(transparent)]
62 CC(#[from] cc::Error),
63 #[error("failed to find cl.exe")]
64 ClNotFound,
65 #[error("failed to find LINK.exe")]
66 LinkNotFound,
67 #[error("source integrity mismatch.\nExpected: {expected},\nbut got: {actual}")]
68 SourceIntegrityMismatch {
69 expected: Integrity,
70 actual: Integrity,
71 },
72 #[error("{name} failed.\n\n{status}\n\nstdout:\n{stdout}\n\nstderr:\n{stderr}")]
73 CommandFailure {
74 name: String,
75 status: ExitStatus,
76 stdout: String,
77 stderr: String,
78 },
79}
80
81impl<State: build_lua_builder::State + build_lua_builder::IsComplete> BuildLuaBuilder<'_, State> {
82 pub async fn build(self) -> Result<(), BuildLuaError> {
83 let args = self._build();
84 let lua_version = args.lua_version;
85 match lua_version {
86 LuaVersion::Lua51
87 | LuaVersion::Lua52
88 | LuaVersion::Lua53
89 | LuaVersion::Lua54
90 | LuaVersion::Lua55 => do_build_lua(args).await,
91 LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => do_build_luajit(args).await,
92 }
93 }
94}
95
96async fn do_build_luajit(args: BuildLua<'_>) -> Result<(), BuildLuaError> {
97 let progress = args.progress;
98
99 let build_dir = tempdir().map_err(|err| {
100 io::Error::other(format!(
101 "failed to create lua_installation temp directory:\n{}",
102 err
103 ))
104 })?;
105 let luajit_url = "https://github.com/LuaJIT/LuaJIT.git";
108 progress.map(|p| p.set_message(format!("🦠 Cloning {luajit_url}")));
109 {
110 let mut fetch_options = FetchOptions::new();
112 fetch_options.update_fetchhead(false);
113 let mut repo_builder = RepoBuilder::new();
114 repo_builder.fetch_options(fetch_options);
115 let repo = repo_builder.clone(luajit_url, build_dir.path())?;
116 let (object, _) = repo.revparse_ext(&format!("v{LUAJIT_MM_VERSION}"))?;
117 repo.checkout_tree(&object, None)?;
118 }
119 if cfg!(target_env = "msvc") {
120 do_build_luajit_msvc(args, build_dir.path()).await
121 } else {
122 do_build_luajit_unix(args, build_dir.path()).await
123 }
124}
125
126async fn do_build_luajit_unix(args: BuildLua<'_>, build_dir: &Path) -> Result<(), BuildLuaError> {
127 let lua_version = args.lua_version;
128 let config = args.config;
129 let install_dir = args.install_dir;
130 let progress = args.progress;
131 progress.map(|p| p.set_message(format!("🛠️ Building Luajit {LUAJIT_MM_VERSION}")));
132
133 let host = Triple::host();
134
135 let mut cc = cc::Build::new();
136 cc.cargo_output(false)
137 .cargo_metadata(false)
138 .cargo_warnings(false)
139 .warnings(config.verbose())
140 .opt_level(3)
141 .host(&host.to_string())
142 .target(&host.to_string());
143 let compiler = cc.try_get_compiler()?;
144 let compiler_path = compiler.path().to_slash_lossy().to_string();
145 let mut make_cmd = Command::new(config.make_cmd());
146 make_cmd.current_dir(build_dir.join("src"));
147 make_cmd.arg("-e");
148 make_cmd.stdout(Stdio::piped());
149 make_cmd.stderr(Stdio::piped());
150 let target = host.to_string();
151 match target.as_str() {
152 "x86_64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
153 make_cmd.env("MACOSX_DEPLOYMENT_TARGET", "10.11");
154 }
155 "aarch64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
156 make_cmd.env("MACOSX_DEPLOYMENT_TARGET", "11.0");
157 }
158 _ if target.contains("linux") => {
159 make_cmd.env("TARGET_SYS", "Linux");
160 }
161 _ => {}
162 }
163 let compiler_path = which::which(&compiler_path)
164 .map_err(|err| io::Error::other(format!("cannot find {}:\n{}", compiler_path, err)))?;
165 let compiler_path = compiler_path.to_slash_lossy().to_string();
166 let compiler_args = compiler.cflags_env();
167 let compiler_args = compiler_args.to_string_lossy();
168 if env::var_os("STATIC_CC").is_none() {
169 make_cmd.env("STATIC_CC", format!("{compiler_path} {compiler_args}"));
170 }
171 if env::var_os("TARGET_LD").is_none() {
172 make_cmd.env("TARGET_LD", format!("{compiler_path} {compiler_args}"));
173 }
174 let mut xcflags = vec!["-fPIC"];
175 if lua_version == &LuaVersion::LuaJIT52 {
176 xcflags.push("-DLUAJIT_ENABLE_LUA52COMPAT");
177 }
178 if cfg!(debug_assertions) {
179 xcflags.push("-DLUA_USE_ASSERT");
180 xcflags.push("-DLUA_USE_APICHECK");
181 }
182 make_cmd.env("BUILDMODE", "static");
183 make_cmd.env("XCFLAGS", xcflags.join(" "));
184
185 match make_cmd.output().await {
186 Ok(output) if output.status.success() => utils::log_command_output(&output, config),
187 Ok(output) => {
188 return Err(BuildLuaError::CommandFailure {
189 name: "build".into(),
190 status: output.status,
191 stdout: String::from_utf8_lossy(&output.stdout).into(),
192 stderr: String::from_utf8_lossy(&output.stderr).into(),
193 });
194 }
195 Err(err) => {
196 return Err(BuildLuaError::Io(io::Error::other(format!(
197 "Failed to run `{} build`:\n{}",
198 config.make_cmd(),
199 err,
200 ))));
201 }
202 };
203
204 progress.map(|p| p.set_message(format!("💻 Installing Luajit {LUAJIT_MM_VERSION}")));
205
206 match Command::new(config.make_cmd())
207 .current_dir(build_dir)
208 .stdout(Stdio::piped())
209 .stderr(Stdio::piped())
210 .arg("install")
211 .arg(format!(r#"PREFIX="{}""#, install_dir.display()))
212 .output()
213 .await
214 {
215 Ok(output) if output.status.success() => utils::log_command_output(&output, config),
216 Ok(output) => {
217 return Err(BuildLuaError::CommandFailure {
218 name: "install".into(),
219 status: output.status,
220 stdout: String::from_utf8_lossy(&output.stdout).into(),
221 stderr: String::from_utf8_lossy(&output.stderr).into(),
222 });
223 }
224 Err(err) => {
225 return Err(BuildLuaError::Io(io::Error::other(format!(
226 "Failed to run `{} install`:\n{}",
227 config.make_cmd(),
228 err,
229 ))));
230 }
231 };
232 move_luajit_includes(install_dir).await?;
233 Ok(())
234}
235
236async fn move_luajit_includes(install_dir: &Path) -> io::Result<()> {
239 let include_dir = install_dir.join("include");
240 let include_subdir = include_dir.join(format!("luajit-{LUAJIT_MM_VERSION}"));
241 if !include_subdir.is_dir() {
242 return Ok(());
243 }
244 let mut dir = fs::read_dir(&include_subdir).await.map_err(|err| {
245 io::Error::other(format!(
246 "Failed to read {}:\n{}",
247 include_subdir.display(),
248 err
249 ))
250 })?;
251 while let Some(entry) = dir.next_entry().await? {
252 let file_name = entry.file_name();
253 let src_path = entry.path();
254 let dest_path = include_dir.join(&file_name);
255 fs::copy(&src_path, &dest_path).await.map_err(|err| {
256 io::Error::other(format!(
257 "error copying {} to {}:\n{}",
258 src_path.display(),
259 dest_path.display(),
260 err
261 ))
262 })?;
263 }
264 fs::remove_dir_all(&include_subdir).await.map_err(|err| {
265 io::Error::other(format!(
266 "Failed to remove {}:\n{}",
267 include_subdir.display(),
268 err
269 ))
270 })?;
271 Ok(())
272}
273
274async fn do_build_luajit_msvc(args: BuildLua<'_>, build_dir: &Path) -> Result<(), BuildLuaError> {
275 let lua_version = args.lua_version;
276 let config = args.config;
277 let install_dir = args.install_dir;
278 let lib_dir = install_dir.join("lib");
279 fs::create_dir_all(&lib_dir).await.map_err(|err| {
280 io::Error::other(format!(
281 "Failed to create directory {}:\n{}",
282 lib_dir.display(),
283 err
284 ))
285 })?;
286 let include_dir = install_dir.join("include");
287 fs::create_dir_all(&include_dir).await.map_err(|err| {
288 io::Error::other(format!(
289 "Failed to create directory {}:\n{}",
290 include_dir.display(),
291 err
292 ))
293 })?;
294 let bin_dir = install_dir.join("bin");
295 fs::create_dir_all(&bin_dir).await.map_err(|err| {
296 io::Error::other(format!(
297 "Failed to create directory {}:\n{}",
298 bin_dir.display(),
299 err
300 ))
301 })?;
302
303 let progress = args.progress;
304
305 progress.map(|p| p.set_message(format!("🛠️ Building Luajit {LUAJIT_MM_VERSION}")));
306
307 let src_dir = build_dir.join("src");
308 let mut msvcbuild = Command::new(src_dir.join("msvcbuild.bat"));
309 msvcbuild.current_dir(&src_dir);
310 if lua_version == &LuaVersion::LuaJIT52 {
311 msvcbuild.arg("lua52compat");
312 }
313 msvcbuild.arg("static");
314 let host = Triple::host();
315 let target = host.to_string();
316 let cl = cc::windows_registry::find_tool(&target, "cl.exe").ok_or(BuildLuaError::ClNotFound)?;
317 for (k, v) in cl.env() {
318 msvcbuild.env(k, v);
319 }
320 fs::create_dir_all(&install_dir).await.map_err(|err| {
321 io::Error::other(format!(
322 "Failed to create directory {}:\n{}",
323 install_dir.display(),
324 err
325 ))
326 })?;
327 match msvcbuild.output().await {
328 Ok(output) if output.status.success() => utils::log_command_output(&output, config),
329 Ok(output) => {
330 return Err(BuildLuaError::CommandFailure {
331 name: "build".into(),
332 status: output.status,
333 stdout: String::from_utf8_lossy(&output.stdout).into(),
334 stderr: String::from_utf8_lossy(&output.stderr).into(),
335 });
336 }
337 Err(err) => {
338 return Err(BuildLuaError::Io(io::Error::other(format!(
339 "Failed to run msvcbuild.bat:\n{}",
340 err,
341 ))))
342 }
343 };
344
345 progress.map(|p| p.set_message(format!("💻 Installing Luajit {LUAJIT_MM_VERSION}")));
346 copy_includes(&src_dir, &include_dir).await?;
347 fs::copy(src_dir.join("lua51.lib"), lib_dir.join("luajit.lib"))
348 .await
349 .map_err(|err| {
350 io::Error::other(format!(
351 "Failed to rename lua51.lib to luajit.lib:\n{}",
352 err
353 ))
354 })?;
355 fs::copy(src_dir.join("luajit.exe"), bin_dir.join("luajit.exe"))
356 .await
357 .map_err(|err| {
358 io::Error::other(format!(
359 "Failed to install luajit.exe to {}:\n{}",
360 bin_dir.display(),
361 err
362 ))
363 })?;
364 Ok(())
365}
366
367async fn do_build_lua(args: BuildLua<'_>) -> Result<(), BuildLuaError> {
368 let lua_version = args.lua_version;
369 let progress = args.progress;
370
371 let build_dir = tempdir().map_err(|err| {
372 io::Error::other(format!(
373 "failed to create lua_installation temp directory:\n{}",
374 err
375 ))
376 })?;
377
378 let (source_integrity, pkg_version): (Integrity, &str) = unsafe {
379 match lua_version {
380 LuaVersion::Lua51 => (LUA51_HASH.parse().unwrap_unchecked(), LUA51_VERSION),
381 LuaVersion::Lua52 => (LUA52_HASH.parse().unwrap_unchecked(), LUA52_VERSION),
382 LuaVersion::Lua53 => (LUA53_HASH.parse().unwrap_unchecked(), LUA53_VERSION),
383 LuaVersion::Lua54 => (LUA54_HASH.parse().unwrap_unchecked(), LUA54_VERSION),
384 LuaVersion::Lua55 => (LUA55_HASH.parse().unwrap_unchecked(), LUA55_VERSION),
385 LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => unreachable!(),
386 }
387 };
388
389 let file_name = format!("lua-{pkg_version}.tar.gz");
390
391 let source_url: Url = unsafe {
392 format!("https://www.lua.org/ftp/{file_name}")
393 .parse()
394 .unwrap_unchecked()
395 };
396
397 progress.map(|p| p.set_message(format!("📥 Downloading {}", source_url)));
398
399 let response = crate::reqwest::new_https_client(args.config)?
400 .get(source_url)
401 .send()
402 .await?
403 .error_for_status()?
404 .bytes()
405 .await?;
406
407 let hash = response.hash()?;
408
409 if hash.matches(&source_integrity).is_none() {
410 return Err(BuildLuaError::SourceIntegrityMismatch {
411 expected: source_integrity,
412 actual: hash,
413 });
414 }
415
416 let cursor = Cursor::new(response);
417 let mime_type = infer::get(cursor.get_ref()).map(|file_type| file_type.mime_type());
418 operations::unpack::unpack(
419 mime_type,
420 cursor,
421 true,
422 file_name,
423 build_dir.path(),
424 progress,
425 )
426 .await?;
427
428 if cfg!(target_env = "msvc") {
429 do_build_lua_msvc(args, build_dir.path(), lua_version, pkg_version).await
430 } else {
431 do_build_lua_unix(args, build_dir.path(), lua_version, pkg_version).await
432 }
433}
434
435async fn do_build_lua_unix(
436 args: BuildLua<'_>,
437 build_dir: &Path,
438 lua_version: &LuaVersion,
439 pkg_version: &str,
440) -> Result<(), BuildLuaError> {
441 let config = args.config;
442 let progress = args.progress;
443 let install_dir = args.install_dir;
444
445 let build_target = if cfg!(target_os = "linux") {
446 if matches!(&lua_version, LuaVersion::Lua54) {
448 "linux-readline"
449 } else {
450 "linux"
451 }
452 } else if cfg!(target_os = "macos") {
453 "macosx"
454 } else if cfg!(target_os = "freebsd") {
455 "freebsd"
456 } else {
457 "generic"
458 };
459
460 progress.map(|p| {
461 p.set_message(format!(
462 "🛠️ Building Lua {} ({})",
463 pkg_version, build_target
464 ))
465 });
466
467 match Command::new(config.make_cmd())
468 .current_dir(build_dir)
469 .stdout(Stdio::piped())
470 .stderr(Stdio::piped())
471 .arg(build_target)
472 .output()
473 .await
474 {
475 Ok(output) if !output.status.success() && build_target != "generic" => {
479 progress.map(|p| {
480 p.println(format!(
481 r#"⚠️ WARNING: Could not build Lua target '{build_target}'. Attempting a 'generic' Lua target build.
482Some functionality may be limited, including dynamic module loading and REPL history.
483
484stderr:
485{}
486"#,
487 String::from_utf8_lossy(&output.stderr)
488 ))
489 });
490 let fallback_output = Command::new(config.make_cmd())
491 .current_dir(build_dir)
492 .stdout(Stdio::piped())
493 .stderr(Stdio::piped())
494 .arg("generic")
495 .output()
496 .await;
497 guard_success(fallback_output, config, "build (generic)")?;
498 }
499 output => guard_success(output, config, &format!("build ({build_target})"))?,
500 };
501
502 progress.map(|p| p.set_message(format!("💻 Installing Lua {}", pkg_version)));
503
504 match Command::new(config.make_cmd())
505 .current_dir(build_dir)
506 .stdout(Stdio::piped())
507 .stderr(Stdio::piped())
508 .arg("install")
509 .arg(format!(r#"INSTALL_TOP="{}""#, install_dir.display()))
510 .output()
511 .await
512 {
513 Ok(output) if output.status.success() => utils::log_command_output(&output, config),
514 Ok(output) => {
515 return Err(BuildLuaError::CommandFailure {
516 name: "install".into(),
517 status: output.status,
518 stdout: String::from_utf8_lossy(&output.stdout).into(),
519 stderr: String::from_utf8_lossy(&output.stderr).into(),
520 });
521 }
522 Err(err) => {
523 return Err(BuildLuaError::Io(io::Error::other(format!(
524 "Failed to run `{} install`:\n{}",
525 config.make_cmd(),
526 err,
527 ))))
528 }
529 };
530
531 Ok(())
532}
533
534async fn do_build_lua_msvc(
535 args: BuildLua<'_>,
536 build_dir: &Path,
537 lua_version: &LuaVersion,
538 pkg_version: &str,
539) -> Result<(), BuildLuaError> {
540 let config = args.config;
541 let progress = args.progress;
542 let install_dir = args.install_dir;
543
544 progress.map(|p| p.set_message(format!("🛠️ Building Lua {}", pkg_version)));
545
546 let lib_dir = install_dir.join("lib");
547 fs::create_dir_all(&lib_dir).await.map_err(|err| {
548 io::Error::other(format!(
549 "Failed to create directory {}:\n{}",
550 lib_dir.display(),
551 err
552 ))
553 })?;
554 let include_dir = install_dir.join("include");
555 fs::create_dir_all(&include_dir).await.map_err(|err| {
556 io::Error::other(format!(
557 "Failed to create directory {}:\n{}",
558 include_dir.display(),
559 err
560 ))
561 })?;
562 let bin_dir = install_dir.join("bin");
563 fs::create_dir_all(&bin_dir).await.map_err(|err| {
564 io::Error::other(format!(
565 "Failed to create directory {}:\n{}",
566 bin_dir.display(),
567 err
568 ))
569 })?;
570
571 let src_dir = build_dir.join("src");
572
573 let lua_bin_name = "lua";
574 let lua_c_bin_name = "luac";
575
576 let dll_name = match lua_version {
577 LuaVersion::Lua51 => "lua51",
578 LuaVersion::Lua52 => "lua52",
579 LuaVersion::Lua53 => "lua53",
580 LuaVersion::Lua54 => "lua54",
581 LuaVersion::Lua55 => "lua55",
582 LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => unreachable!(),
583 };
584
585 let lib_name = match lua_version {
586 LuaVersion::Lua51 => "lua5.1",
587 LuaVersion::Lua52 => "lua5.2",
588 LuaVersion::Lua53 => "lua5.3",
589 LuaVersion::Lua54 => "lua5.4",
590 LuaVersion::Lua55 => "lua5.5",
591 LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => unreachable!(),
592 };
593
594 let host = Triple::host();
595 let mut cc = cc::Build::new();
596 cc.cargo_output(false)
597 .cargo_metadata(false)
598 .cargo_warnings(false)
599 .warnings(config.verbose())
600 .opt_level(3)
601 .host(&host.to_string())
602 .target(&host.to_string());
603
604 cc.define("LUA_USE_WINDOWS", None);
605 cc.define("LUA_BUILD_AS_DLL", None);
606
607 let mut lib_c_files = Vec::new();
608 let mut read_dir = fs::read_dir(&src_dir).await.map_err(|err| {
609 io::Error::other(format!(
610 "Failed to read directory {}:\n{}",
611 src_dir.display(),
612 err
613 ))
614 })?;
615 while let Some(entry) = read_dir.next_entry().await? {
616 let path = entry.path();
617 if path.extension().is_some_and(|ext| ext == "c")
618 && path
619 .with_extension("")
620 .file_name()
621 .is_some_and(|name| name != "lua" && name != "luac")
622 {
623 lib_c_files.push(path);
624 }
625 }
626
627 let lib_objects = cc
628 .include(&src_dir)
629 .files(lib_c_files)
630 .out_dir(&lib_dir)
631 .try_compile_intermediates()?;
632
633 let bin_objects = cc
634 .include(&src_dir)
635 .file(src_dir.join(format!("{lua_bin_name}.c")))
636 .file(src_dir.join(format!("{lua_c_bin_name}.c")))
637 .out_dir(&src_dir)
638 .try_compile_intermediates()?;
639
640 let lua_bin_objects = bin_objects.iter().filter(|file| {
641 file.file_stem().is_some_and(|fname| {
642 fname
643 .to_string_lossy()
644 .ends_with(&format!("-{lua_bin_name}"))
645 })
646 });
647
648 let lua_c_bin_objects = bin_objects.iter().filter(|file| {
649 file.file_stem().is_some_and(|fname| {
650 fname
651 .to_string_lossy()
652 .ends_with(&format!("-{lua_c_bin_name}"))
653 })
654 });
655
656 progress.map(|p| p.set_message(format!("💻 Installing Lua {}", pkg_version)));
657
658 let target = host.to_string();
659 let link =
660 cc::windows_registry::find_tool(&target, "link.exe").ok_or(BuildLuaError::LinkNotFound)?;
661
662 let dll_path = bin_dir.join(format!("{dll_name}.dll"));
663 let lua_bin_path = bin_dir.join(format!("{lua_bin_name}.exe"));
664 let lua_c_bin_path = bin_dir.join(format!("{lua_c_bin_name}.exe"));
665
666 let implib_path = lib_dir.join(format!("{lib_name}.lib"));
667
668 guard_success(
670 Command::new(link.path())
671 .arg("/DLL")
672 .arg(format!("/OUT:{}", dll_path.display()))
673 .arg(format!("/IMPLIB:{}", implib_path.display()))
674 .args(&lib_objects)
675 .output()
676 .await,
677 config,
678 &format!("link {dll_name}.dll"),
679 )?;
680
681 guard_success(
683 Command::new(link.path())
684 .arg(format!("/OUT:{}", lua_bin_path.display()))
685 .arg(&implib_path)
686 .args(lua_bin_objects)
687 .output()
688 .await,
689 config,
690 &format!("link {}", lua_bin_path.display()),
691 )?;
692
693 guard_success(
695 Command::new(link.path())
696 .arg(format!("/OUT:{}", lua_c_bin_path.display()))
697 .args(&lib_objects)
698 .args(lua_c_bin_objects)
699 .output()
700 .await,
701 config,
702 &format!("link {}", lua_c_bin_path.display()),
703 )?;
704
705 copy_includes(&src_dir, &include_dir).await?;
706
707 Ok(())
708}
709
710fn guard_success(
711 output: io::Result<std::process::Output>,
712 config: &Config,
713 cmd_name: &str,
714) -> Result<(), BuildLuaError> {
715 match output {
716 Ok(output) if output.status.success() => {
717 utils::log_command_output(&output, config);
718 Ok(())
719 }
720 Ok(output) => Err(BuildLuaError::CommandFailure {
721 name: cmd_name.to_string(),
722 status: output.status,
723 stdout: String::from_utf8_lossy(&output.stdout).into(),
724 stderr: String::from_utf8_lossy(&output.stderr).into(),
725 }),
726 Err(err) => Err(BuildLuaError::Io(io::Error::other(format!(
727 "Failed to run `{} build`:\n{}",
728 config.make_cmd(),
729 err,
730 )))),
731 }
732}
733
734async fn copy_includes(src_dir: &Path, include_dir: &Path) -> Result<(), io::Error> {
735 for f in &[
736 "lauxlib.h",
737 "lua.h",
738 "luaconf.h",
739 "luajit.h",
740 "lualib.h",
741 "lua.hpp",
742 ] {
743 let src_file = src_dir.join(f);
744 if src_file.is_file() {
745 fs::copy(&src_file, include_dir.join(f))
746 .await
747 .map_err(|err| {
748 io::Error::other(format!(
749 "Failed to copy {} to {}:\n{}",
750 src_file.display(),
751 include_dir.display(),
752 err
753 ))
754 })?;
755 }
756 }
757 Ok(())
758}