lux_lib/build/
rust_mlua.rs1use crate::build::backend::{BuildBackend, BuildInfo, RunBuildArgs};
2use crate::build::{cargo, command};
3use crate::config::build;
4use crate::fs;
5use crate::lua_rockspec::RustMluaBuildSpec;
6use crate::lua_version::{LuaVersion, LuaVersionUnset};
7use crate::target::c_dylib_extension;
8use crate::tree::InstallTree;
9use itertools::Itertools;
10use miette::Diagnostic;
11use std::collections::HashMap;
12use std::io;
13use std::path::{Path, PathBuf};
14use std::process::ExitStatus;
15use thiserror::Error;
16
17use tracing::Instrument;
18
19#[derive(Error, Debug, Diagnostic)]
20#[non_exhaustive]
21pub enum Error {
22 #[error("`cargo build` failed.\nstatus: {status}\nstdout: {stdout}\nstderr: {stderr}")]
23 CargoBuild {
24 status: ExitStatus,
25 stdout: String,
26 stderr: String,
27 },
28 #[error("failed to run `cargo build`")]
29 RustBuild { source: io::Error },
30 #[error(transparent)]
31 #[diagnostic(transparent)]
32 LuaVersionUnset(#[from] LuaVersionUnset),
33 #[error(transparent)]
34 #[diagnostic(transparent)]
35 Fs(#[from] fs::Error),
36 #[error("luau does not support rust-mlua dependencies")]
37 LuauUnsupported,
38}
39
40impl BuildBackend for RustMluaBuildSpec {
41 type Err = Error;
42
43 #[tracing::instrument(name = "rust_mlua::run", skip_all, level = "debug")]
44 async fn run<T>(self, args: RunBuildArgs<'_, T>) -> Result<BuildInfo, Self::Err>
45 where
46 T: InstallTree,
47 {
48 let package = args.package;
49 let config = args.config;
50 let tree = args.tree;
51 let layout = tree.layout_for(package);
52 let build_dir = args.build_dir;
53 let lua_version = LuaVersion::from(config)?;
54 let lua_feature = match lua_version {
55 LuaVersion::Lua51 => "lua51",
56 LuaVersion::Lua52 => "lua52",
57 LuaVersion::Lua53 => "lua53",
58 LuaVersion::Lua54 => "lua54",
59 LuaVersion::Lua55 => "lua55",
60 LuaVersion::LuaJIT => "luajit",
61 LuaVersion::LuaJIT52 => "luajit",
62 LuaVersion::Luau => return Err(Error::LuauUnsupported),
63 };
64 let features = self
65 .features
66 .into_iter()
67 .chain(std::iter::once(lua_feature.into()))
68 .join(",");
69 let target_dir_arg = format!("--target-dir={}", self.target_path.display());
70 let mut build_args = vec!["build"];
71 if config.build_profile() == build::Profile::Release {
72 build_args.push("--release");
73 }
74 build_args.push(&target_dir_arg);
75 if !self.default_features {
76 build_args.push("--no-default-features");
77 }
78 build_args.push("--features");
79 build_args.push(&features);
80 build_args.extend(self.cargo_extra_args.iter().map(|arg| arg.as_str()));
81
82 if let Some(cargo_vendor_dir) = config
83 .vendor_dir()
84 .map(|vendor_dir| vendor_dir.join("cargo"))
85 .filter(|dir| dir.is_dir())
86 {
87 cargo::prepare_vendor_config(config, build_dir, &cargo_vendor_dir).await?;
88 build_args.push("--offline");
89 }
90
91 {
92 match config
93 .wrapped_command("cargo", build_args)
94 .current_dir(build_dir)
95 .output()
96 .instrument(tracing::info_span!(
97 "Compiling rust-mlua module",
98 profile = config.build_profile().to_string()
99 ))
100 .await
101 {
102 Ok(output) if output.status.success() => command::trace_output(&output),
103 Ok(output) => {
104 return Err(Error::CargoBuild {
105 status: output.status,
106 stdout: String::from_utf8_lossy(&output.stdout).into(),
107 stderr: String::from_utf8_lossy(&output.stderr).into(),
108 });
109 }
110 Err(source) => return Err(Error::RustBuild { source }),
111 }
112 }
113 fs::tokio::create_dir_all(&layout.lib).await?;
114 let profile_dir = match config.build_profile() {
115 build::Profile::Release => "release",
116 build::Profile::Dev => "debug",
117 };
118 if let Err(err) = install_rust_libs(
119 self.modules,
120 &self.target_path,
121 build_dir,
122 &layout.lib,
123 profile_dir,
124 )
125 .await
126 {
127 cleanup(&layout.root).await;
128 return Err(err.into());
129 }
130 fs::tokio::create_dir_all(&layout.src).await?;
131 if let Err(err) = install_lua_libs(self.include, build_dir, &layout.src).await {
132 cleanup(&layout.root).await;
133 return Err(err.into());
134 }
135 Ok(BuildInfo::default())
136 }
137}
138
139#[tracing::instrument(level = "trace")]
140async fn install_rust_libs(
141 modules: HashMap<String, PathBuf>,
142 target_path: &Path,
143 build_dir: &Path,
144 lib_dir: &Path,
145 profile_dir: &str,
146) -> Result<(), fs::Error> {
147 for (module, rust_lib) in modules {
148 let src = build_dir.join(target_path).join(profile_dir).join(rust_lib);
149 let mut dst: PathBuf = lib_dir.join(module);
150 dst.set_extension(c_dylib_extension());
151 fs::tokio::copy(&src, &dst).await?;
152 }
153 Ok(())
154}
155
156#[tracing::instrument(level = "trace")]
157async fn install_lua_libs(
158 include: HashMap<PathBuf, PathBuf>,
159 build_dir: &Path,
160 src_dir: &Path,
161) -> Result<(), fs::Error> {
162 for (from, to) in include {
163 let src = build_dir.join(from);
164 let dst = src_dir.join(to);
165 fs::tokio::copy(&src, &dst).await?;
166 }
167 Ok(())
168}
169
170#[tracing::instrument(level = "trace")]
171async fn cleanup(root_dir: &Path) -> () {
172 match fs::tokio::remove_dir_all(root_dir).await {
173 Ok(_) => (),
174 Err(err) => tracing::warn!("failed to clean up {}: {}", root_dir.display(), err),
175 };
176}