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=";
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}
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 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(); 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
251async 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#[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 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 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 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 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 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}