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