Skip to main content

lux_lib/build/
rust_mlua.rs

1use 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}