Skip to main content

lux_lib/operations/
build_lua.rs

1use std::{
2    env,
3    io::{self, Cursor},
4    path::Path,
5    process::{ExitStatus, Stdio},
6};
7
8use crate::{
9    build::utils::{self, CCExt},
10    config::Config,
11    fs,
12    hash::HasIntegrity,
13    lua_version::LuaVersion,
14    operations::{self, UnpackError},
15};
16use bon::Builder;
17use git2::{build::RepoBuilder, FetchOptions};
18use miette::Diagnostic;
19use path_slash::PathExt;
20use ssri::Integrity;
21use target_lexicon::Triple;
22use thiserror::Error;
23use tokio::process::Command;
24use tracing::{span, Instrument};
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=";
37// XXX: there's no tag with lua 5.2 compatibility, so we have to use the v2.1 branch for now
38// this is unstable and might break the build.
39const 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}
48
49#[derive(Debug, Error, Diagnostic)]
50pub enum BuildLuaError {
51    #[error(transparent)]
52    Request(#[from] reqwest::Error),
53    #[error(transparent)]
54    Io(#[from] io::Error),
55    #[error(transparent)]
56    #[diagnostic(transparent)]
57    Fs(#[from] fs::FsError),
58    #[error(transparent)]
59    #[diagnostic(transparent)]
60    Unpack(#[from] UnpackError),
61    #[error(transparent)]
62    Git(#[from] git2::Error),
63    #[error(transparent)]
64    CC(#[from] cc::Error),
65    #[error("failed to find cl.exe")]
66    ClNotFound,
67    #[error("failed to find LINK.exe")]
68    LinkNotFound,
69    #[error(
70        r#"source integrity mismatch.
71- source: {src}
72- expected: {expected}
73- got: {actual}"#
74    )]
75    #[diagnostic(help(
76        r#"the source may have been modified or a tag may have been moved.
77check the source, then rerun the command with `--no-lock` to update the hash."#
78    ))]
79    SourceIntegrityMismatch {
80        src: String,
81        expected: Integrity,
82        actual: Integrity,
83    },
84    #[error("{name} failed.\n\n{status}\n\nstdout:\n{stdout}\n\nstderr:\n{stderr}")]
85    CommandFailure {
86        name: String,
87        status: ExitStatus,
88        stdout: String,
89        stderr: String,
90    },
91}
92
93impl<State: build_lua_builder::State + build_lua_builder::IsComplete> BuildLuaBuilder<'_, State> {
94    pub async fn build(self) -> Result<(), BuildLuaError> {
95        let args = self._build();
96        let lua_version = args.lua_version;
97        match lua_version {
98            LuaVersion::Lua51
99            | LuaVersion::Lua52
100            | LuaVersion::Lua53
101            | LuaVersion::Lua54
102            | LuaVersion::Lua55 => do_build_lua(args).await,
103            LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => do_build_luajit(args).await,
104        }
105    }
106}
107
108#[tracing::instrument(
109    name = "Building LuaJIT",
110    level = "info",
111    skip_all,
112    fields(version = LUAJIT_MM_VERSION),
113)]
114async fn do_build_luajit(args: BuildLua<'_>) -> Result<(), BuildLuaError> {
115    let build_dir = fs::tempfile::tempdir()?;
116    // XXX luajit.org responds with an invalid content-type, so we'll use the github mirror for now.
117    // let luajit_url = "https://luajit.org/git/luajit.git";
118    let luajit_url = "https://github.com/LuaJIT/LuaJIT.git";
119
120    {
121        let span = span!(
122            tracing::Level::INFO,
123            "Cloning LuaJIT sources",
124            url = luajit_url,
125        );
126        let _enter = span.enter(); // OK because we're not in an async block
127
128        // We create a new scope because we have to drop fetch_options before the await
129        let mut fetch_options = FetchOptions::new();
130        fetch_options.update_fetchhead(false);
131        let mut repo_builder = RepoBuilder::new();
132        repo_builder.fetch_options(fetch_options);
133        let repo = repo_builder.clone(luajit_url, build_dir.path())?;
134        let (object, _) = repo.revparse_ext(&format!("v{LUAJIT_MM_VERSION}"))?;
135        repo.checkout_tree(&object, None)?;
136    }
137    if cfg!(target_env = "msvc") {
138        do_build_luajit_msvc(args, build_dir.path()).await
139    } else {
140        do_build_luajit_unix(args, build_dir.path()).await
141    }
142}
143
144#[tracing::instrument(name = "Compiling LuaJIT", skip_all)]
145async fn do_build_luajit_unix(args: BuildLua<'_>, build_dir: &Path) -> Result<(), BuildLuaError> {
146    let lua_version = args.lua_version;
147    let config = args.config;
148    let install_dir = args.install_dir;
149
150    let host = Triple::host();
151
152    let mut cc = cc::Build::new();
153    cc.cargo_output(false)
154        .cargo_metadata(false)
155        .cargo_warnings(false)
156        .warnings(config.verbose())
157        .opt_level(3)
158        .host(&host.to_string())
159        .target(&host.to_string());
160    let compiler = cc.try_get_compiler()?;
161    let compiler_path = compiler.path().to_slash_lossy().to_string();
162    let mut make_cmd = Command::new(config.make_cmd());
163    make_cmd.current_dir(build_dir.join("src"));
164    make_cmd.arg("-e");
165    make_cmd.stdout(Stdio::piped());
166    make_cmd.stderr(Stdio::piped());
167    let target = host.to_string();
168    match target.as_str() {
169        "x86_64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
170            make_cmd.env("MACOSX_DEPLOYMENT_TARGET", "10.11");
171        }
172        "aarch64-apple-darwin" if env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none() => {
173            make_cmd.env("MACOSX_DEPLOYMENT_TARGET", "11.0");
174        }
175        _ if target.contains("linux") => {
176            make_cmd.env("TARGET_SYS", "Linux");
177        }
178        _ => {}
179    }
180    let compiler_path = which::which(&compiler_path)
181        .map_err(|err| io::Error::other(format!("cannot find {}:\n{}", compiler_path, err)))?;
182    let compiler_path = compiler_path.to_slash_lossy().to_string();
183    let compiler_args = compiler.cflags_env();
184    let compiler_args = compiler_args.to_string_lossy();
185    if env::var_os("STATIC_CC").is_none() {
186        make_cmd.env("STATIC_CC", format!("{compiler_path} {compiler_args}"));
187    }
188    if env::var_os("TARGET_LD").is_none() {
189        make_cmd.env("TARGET_LD", format!("{compiler_path} {compiler_args}"));
190    }
191    let mut xcflags = vec!["-fPIC"];
192    if lua_version == &LuaVersion::LuaJIT52 {
193        xcflags.push("-DLUAJIT_ENABLE_LUA52COMPAT");
194    }
195    if cfg!(debug_assertions) {
196        xcflags.push("-DLUA_USE_ASSERT");
197        xcflags.push("-DLUA_USE_APICHECK");
198    }
199    make_cmd.env("BUILDMODE", "static");
200    make_cmd.env("XCFLAGS", xcflags.join(" "));
201
202    match make_cmd.output().await {
203        Ok(output) if output.status.success() => utils::trace_command_output(&output),
204        Ok(output) => {
205            return Err(BuildLuaError::CommandFailure {
206                name: "build".into(),
207                status: output.status,
208                stdout: String::from_utf8_lossy(&output.stdout).into(),
209                stderr: String::from_utf8_lossy(&output.stderr).into(),
210            });
211        }
212        Err(err) => {
213            return Err(BuildLuaError::Io(io::Error::other(format!(
214                "Failed to run `{} build`:\n{}",
215                config.make_cmd(),
216                err
217            ))));
218        }
219    };
220
221    match Command::new(config.make_cmd())
222        .current_dir(build_dir)
223        .stdout(Stdio::piped())
224        .stderr(Stdio::piped())
225        .arg("install")
226        .arg(format!(r#"PREFIX="{}""#, install_dir.display()))
227        .output()
228        .await
229    {
230        Ok(output) if output.status.success() => utils::trace_command_output(&output),
231        Ok(output) => {
232            return Err(BuildLuaError::CommandFailure {
233                name: "install".into(),
234                status: output.status,
235                stdout: String::from_utf8_lossy(&output.stdout).into(),
236                stderr: String::from_utf8_lossy(&output.stderr).into(),
237            });
238        }
239        Err(err) => {
240            return Err(BuildLuaError::Io(io::Error::other(format!(
241                "Failed to run `{} install`:\n{}",
242                config.make_cmd(),
243                err
244            ))));
245        }
246    };
247    move_luajit_includes(install_dir).await?;
248    Ok(())
249}
250
251/// luajit installs the includes to a subdirectory.
252/// For consistency, we want them in the `include` directory
253async fn move_luajit_includes(install_dir: &Path) -> io::Result<()> {
254    let include_dir = install_dir.join("include");
255    let include_subdir = include_dir.join(format!("luajit-{LUAJIT_MM_VERSION}"));
256    if !include_subdir.is_dir() {
257        return Ok(());
258    }
259    let mut dir = fs::tokio::read_dir(&include_subdir)
260        .await
261        .map_err(|err| io::Error::other(err.to_string()))?;
262    while let Some(entry) = dir.next_entry().await? {
263        let file_name = entry.file_name();
264        let src_path = entry.path();
265        let dest_path = include_dir.join(&file_name);
266        fs::tokio::copy(&src_path, &dest_path)
267            .await
268            .map_err(|err| io::Error::other(err.to_string()))?;
269    }
270    fs::tokio::remove_dir_all(&include_subdir)
271        .await
272        .map_err(|err| io::Error::other(err.to_string()))?;
273    Ok(())
274}
275
276// NOTE: LuaJIT must be built as a shared library on MSVC.
277// https://luajit.org/install.html: "You may link LuaJIT statically on Windows
278// only if you don't intend to load Lua/C modules at runtime."
279#[tracing::instrument(name = "Compiling LuaJIT", skip_all)]
280async fn do_build_luajit_msvc(args: BuildLua<'_>, build_dir: &Path) -> Result<(), BuildLuaError> {
281    let lua_version = args.lua_version;
282    let install_dir = args.install_dir;
283    let lib_dir = install_dir.join("lib");
284    fs::tokio::create_dir_all(&lib_dir)
285        .await
286        .map_err(|err| io::Error::other(err.to_string()))?;
287    let include_dir = install_dir.join("include");
288    fs::tokio::create_dir_all(&include_dir)
289        .await
290        .map_err(|err| io::Error::other(err.to_string()))?;
291    let bin_dir = install_dir.join("bin");
292    fs::tokio::create_dir_all(&bin_dir)
293        .await
294        .map_err(|err| io::Error::other(err.to_string()))?;
295
296    let src_dir = build_dir.join("src");
297    let mut msvcbuild = Command::new(src_dir.join("msvcbuild.bat"));
298    msvcbuild.current_dir(&src_dir);
299    if lua_version == &LuaVersion::LuaJIT52 {
300        msvcbuild.arg("lua52compat");
301    }
302    let host = Triple::host();
303    let target = host.to_string();
304    let cl = cc::windows_registry::find_tool(&target, "cl.exe").ok_or(BuildLuaError::ClNotFound)?;
305    for (k, v) in cl.env() {
306        msvcbuild.env(k, v);
307    }
308    fs::tokio::create_dir_all(&install_dir)
309        .await
310        .map_err(|err| io::Error::other(err.to_string()))?;
311    match msvcbuild.output().await {
312        Ok(output) if output.status.success() => utils::trace_command_output(&output),
313        Ok(output) => {
314            return Err(BuildLuaError::CommandFailure {
315                name: "build".into(),
316                status: output.status,
317                stdout: String::from_utf8_lossy(&output.stdout).into(),
318                stderr: String::from_utf8_lossy(&output.stderr).into(),
319            });
320        }
321        Err(err) => {
322            return Err(BuildLuaError::Io(io::Error::other(format!(
323                "Failed to run msvcbuild.bat:\n{}",
324                err
325            ))))
326        }
327    };
328
329    copy_includes(&src_dir, &include_dir).await?;
330    fs::tokio::copy(src_dir.join("lua51.lib"), lib_dir.join("luajit.lib"))
331        .await
332        .map_err(|err| io::Error::other(err.to_string()))?;
333    fs::tokio::copy(src_dir.join("luajit.exe"), bin_dir.join("luajit.exe"))
334        .await
335        .map_err(|err| io::Error::other(err.to_string()))?;
336    fs::tokio::copy(src_dir.join("lua51.dll"), bin_dir.join("lua51.dll"))
337        .await
338        .map_err(|err| io::Error::other(err.to_string()))?;
339    fs::tokio::copy(src_dir.join("lua51.dll"), lib_dir.join("lua51.dll"))
340        .await
341        .map_err(|err| io::Error::other(err.to_string()))?;
342    Ok(())
343}
344
345#[tracing::instrument(
346    name = "Building Lua",
347    level = "info",
348    skip_all,
349    fields(version = args.lua_version.to_string()),
350)]
351async fn do_build_lua(args: BuildLua<'_>) -> Result<(), BuildLuaError> {
352    let lua_version = args.lua_version;
353    let build_dir = fs::tempfile::tempdir()?;
354
355    let (source_integrity, pkg_version): (Integrity, &str) = unsafe {
356        match lua_version {
357            LuaVersion::Lua51 => (LUA51_HASH.parse().unwrap_unchecked(), LUA51_VERSION),
358            LuaVersion::Lua52 => (LUA52_HASH.parse().unwrap_unchecked(), LUA52_VERSION),
359            LuaVersion::Lua53 => (LUA53_HASH.parse().unwrap_unchecked(), LUA53_VERSION),
360            LuaVersion::Lua54 => (LUA54_HASH.parse().unwrap_unchecked(), LUA54_VERSION),
361            LuaVersion::Lua55 => (LUA55_HASH.parse().unwrap_unchecked(), LUA55_VERSION),
362            LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => unreachable!(),
363        }
364    };
365
366    let file_name = format!("lua-{pkg_version}.tar.gz");
367
368    let source_url: Url = unsafe {
369        format!("https://www.lua.org/ftp/{file_name}")
370            .parse()
371            .unwrap_unchecked()
372    };
373
374    let response = crate::reqwest::https_client(args.config)?
375        .get(source_url.clone())
376        .send()
377        .instrument(tracing::info_span!("Downloading Lua"))
378        .await?
379        .error_for_status()?
380        .bytes()
381        .instrument(tracing::trace_span!("getting response body"))
382        .await?;
383
384    let hash = response.hash().await?;
385
386    if hash.matches(&source_integrity).is_none() {
387        return Err(BuildLuaError::SourceIntegrityMismatch {
388            src: source_url.to_string(),
389            expected: source_integrity,
390            actual: hash,
391        });
392    }
393
394    let cursor = Cursor::new(response);
395    let mime_type = infer::get(cursor.get_ref()).map(|file_type| file_type.mime_type());
396    operations::unpack::unpack(mime_type, cursor, true, file_name, build_dir.path()).await?;
397
398    if cfg!(target_env = "msvc") {
399        do_build_lua_msvc(args, build_dir.path(), lua_version, pkg_version).await
400    } else {
401        do_build_lua_unix(args, build_dir.path(), lua_version, pkg_version).await
402    }
403}
404
405#[tracing::instrument(name = "Compiling Lua", skip_all)]
406async fn do_build_lua_unix(
407    args: BuildLua<'_>,
408    build_dir: &Path,
409    lua_version: &LuaVersion,
410    _pkg_version: &str,
411) -> Result<(), BuildLuaError> {
412    tracing::debug!(message = "Compiling Lua (Unix)...");
413    let config = args.config;
414    let install_dir = args.install_dir;
415
416    let build_target = if cfg!(target_os = "linux") {
417        // only lua 5.4 has a specific `linux-readline` target
418        if matches!(&lua_version, LuaVersion::Lua54) {
419            "linux-readline"
420        } else {
421            "linux"
422        }
423    } else if cfg!(target_os = "macos") {
424        "macosx"
425    } else if cfg!(target_os = "freebsd") {
426        "freebsd"
427    } else {
428        "generic"
429    };
430    match Command::new(config.make_cmd())
431        .current_dir(build_dir)
432        .stdout(Stdio::piped())
433        .stderr(Stdio::piped())
434        .arg(build_target)
435        .output()
436        .await
437    {
438        // The lua build may fail for some platform specific reason.
439        // Typically, the `readline` headers cannot be found. In these
440        // cases, try the build again with the `generic` platform target.
441        Ok(output) if !output.status.success() && build_target != "generic" => {
442            let fallback_output = Command::new(config.make_cmd())
443                .current_dir(build_dir)
444                .stdout(Stdio::piped())
445                .stderr(Stdio::piped())
446                .arg("generic")
447                .output()
448                .await;
449            guard_success(fallback_output, config, "build (generic)")?;
450        }
451        output => guard_success(output, config, &format!("build ({build_target})"))?,
452    };
453
454    match Command::new(config.make_cmd())
455        .current_dir(build_dir)
456        .stdout(Stdio::piped())
457        .stderr(Stdio::piped())
458        .arg("install")
459        .arg(format!(r#"INSTALL_TOP="{}""#, install_dir.display()))
460        .output()
461        .await
462    {
463        Ok(output) if output.status.success() => utils::trace_command_output(&output),
464        Ok(output) => {
465            return Err(BuildLuaError::CommandFailure {
466                name: "install".into(),
467                status: output.status,
468                stdout: String::from_utf8_lossy(&output.stdout).into(),
469                stderr: String::from_utf8_lossy(&output.stderr).into(),
470            });
471        }
472        Err(err) => {
473            return Err(BuildLuaError::Io(io::Error::other(format!(
474                "Failed to run `{} install`:\n{}",
475                config.make_cmd(),
476                err
477            ))))
478        }
479    };
480
481    Ok(())
482}
483
484#[tracing::instrument(name = "Compiling Lua", skip_all)]
485async fn do_build_lua_msvc(
486    args: BuildLua<'_>,
487    build_dir: &Path,
488    lua_version: &LuaVersion,
489    _pkg_version: &str,
490) -> Result<(), BuildLuaError> {
491    tracing::debug!(message = "Compiling Lua (MSVC)...");
492    let config = args.config;
493    let install_dir = args.install_dir;
494
495    let lib_dir = install_dir.join("lib");
496    fs::tokio::create_dir_all(&lib_dir)
497        .await
498        .map_err(|err| io::Error::other(err.to_string()))?;
499    let include_dir = install_dir.join("include");
500    fs::tokio::create_dir_all(&include_dir)
501        .await
502        .map_err(|err| io::Error::other(err.to_string()))?;
503    let bin_dir = install_dir.join("bin");
504    fs::tokio::create_dir_all(&bin_dir)
505        .await
506        .map_err(|err| io::Error::other(err.to_string()))?;
507
508    let src_dir = build_dir.join("src");
509
510    let lua_bin_name = "lua";
511    let lua_c_bin_name = "luac";
512
513    let dll_name = match lua_version {
514        LuaVersion::Lua51 => "lua51",
515        LuaVersion::Lua52 => "lua52",
516        LuaVersion::Lua53 => "lua53",
517        LuaVersion::Lua54 => "lua54",
518        LuaVersion::Lua55 => "lua55",
519        LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => unreachable!(),
520    };
521
522    let lib_name = match lua_version {
523        LuaVersion::Lua51 => "lua5.1",
524        LuaVersion::Lua52 => "lua5.2",
525        LuaVersion::Lua53 => "lua5.3",
526        LuaVersion::Lua54 => "lua5.4",
527        LuaVersion::Lua55 => "lua5.5",
528        LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => unreachable!(),
529    };
530
531    let host = Triple::host();
532    let mut cc = cc::Build::new();
533    cc.cargo_output(false)
534        .cargo_metadata(false)
535        .cargo_warnings(false)
536        .warnings(config.verbose())
537        .opt_level(3)
538        .host(&host.to_string())
539        .target(&host.to_string());
540
541    cc.define("LUA_USE_WINDOWS", None);
542    cc.define("LUA_BUILD_AS_DLL", None);
543
544    let mut lib_c_files = Vec::new();
545    let mut read_dir = fs::tokio::read_dir(&src_dir)
546        .await
547        .map_err(|err| io::Error::other(err.to_string()))?;
548    while let Some(entry) = read_dir.next_entry().await? {
549        let path = entry.path();
550        if path.extension().is_some_and(|ext| ext == "c")
551            && path
552                .with_extension("")
553                .file_name()
554                .is_some_and(|name| name != "lua" && name != "luac")
555        {
556            lib_c_files.push(path);
557        }
558    }
559
560    let lib_objects = cc
561        .include(&src_dir)
562        .files(lib_c_files)
563        .out_dir(&lib_dir)
564        .try_compile_objects(config)?;
565
566    let bin_objects = cc
567        .include(&src_dir)
568        .file(src_dir.join(format!("{lua_bin_name}.c")))
569        .file(src_dir.join(format!("{lua_c_bin_name}.c")))
570        .out_dir(&src_dir)
571        .try_compile_objects(config)?;
572
573    let lua_bin_objects = bin_objects.iter().filter(|file| {
574        file.file_stem().is_some_and(|fname| {
575            fname
576                .to_string_lossy()
577                .ends_with(&format!("-{lua_bin_name}"))
578        })
579    });
580
581    let lua_c_bin_objects = bin_objects.iter().filter(|file| {
582        file.file_stem().is_some_and(|fname| {
583            fname
584                .to_string_lossy()
585                .ends_with(&format!("-{lua_c_bin_name}"))
586        })
587    });
588
589    let target = host.to_string();
590    let link =
591        cc::windows_registry::find_tool(&target, "link.exe").ok_or(BuildLuaError::LinkNotFound)?;
592
593    let dll_path = bin_dir.join(format!("{dll_name}.dll"));
594    let lua_bin_path = bin_dir.join(format!("{lua_bin_name}.exe"));
595    let lua_c_bin_path = bin_dir.join(format!("{lua_c_bin_name}.exe"));
596
597    let implib_path = lib_dir.join(format!("{lib_name}.lib"));
598
599    // lua.dll
600    guard_success(
601        Command::new(link.path())
602            .arg("/DLL")
603            .arg(format!("/OUT:{}", dll_path.display()))
604            .arg(format!("/IMPLIB:{}", implib_path.display()))
605            .args(&lib_objects)
606            .output()
607            .await,
608        config,
609        &format!("link {dll_name}.dll"),
610    )?;
611
612    // lua.exe
613    guard_success(
614        Command::new(link.path())
615            .arg(format!("/OUT:{}", lua_bin_path.display()))
616            .arg(&implib_path)
617            .args(lua_bin_objects)
618            .output()
619            .await,
620        config,
621        &format!("link {}", lua_bin_path.display()),
622    )?;
623
624    // luac.exe
625    guard_success(
626        Command::new(link.path())
627            .arg(format!("/OUT:{}", lua_c_bin_path.display()))
628            .args(&lib_objects)
629            .args(lua_c_bin_objects)
630            .output()
631            .await,
632        config,
633        &format!("link {}", lua_c_bin_path.display()),
634    )?;
635
636    copy_includes(&src_dir, &include_dir).await?;
637
638    Ok(())
639}
640
641fn guard_success(
642    output: io::Result<std::process::Output>,
643    config: &Config,
644    cmd_name: &str,
645) -> Result<(), BuildLuaError> {
646    match output {
647        Ok(output) if output.status.success() => {
648            utils::trace_command_output(&output);
649            Ok(())
650        }
651        Ok(output) => Err(BuildLuaError::CommandFailure {
652            name: cmd_name.to_string(),
653            status: output.status,
654            stdout: String::from_utf8_lossy(&output.stdout).into(),
655            stderr: String::from_utf8_lossy(&output.stderr).into(),
656        }),
657        Err(err) => Err(BuildLuaError::Io(io::Error::other(format!(
658            "Failed to run `{} build`:\n{}",
659            config.make_cmd(),
660            err
661        )))),
662    }
663}
664
665async fn copy_includes(src_dir: &Path, include_dir: &Path) -> Result<(), io::Error> {
666    for f in &[
667        "lauxlib.h",
668        "lua.h",
669        "luaconf.h",
670        "luajit.h",
671        "lualib.h",
672        "lua.hpp",
673    ] {
674        let src_file = src_dir.join(f);
675        if src_file.is_file() {
676            fs::tokio::copy(&src_file, include_dir.join(f))
677                .await
678                .map_err(|err| io::Error::other(err.to_string()))?;
679        }
680    }
681    Ok(())
682}