use super::utils::c_dylib_extension;
use crate::build::backend::{BuildBackend, BuildInfo, RunBuildArgs};
use crate::lua_version::{LuaVersion, LuaVersionUnset};
use crate::progress::{Progress, ProgressBar};
use crate::{lua_rockspec::RustMluaBuildSpec, tree::RockLayout};
use itertools::Itertools;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use std::{fs, io};
use thiserror::Error;
use tokio::process::Command;
#[derive(Error, Debug)]
pub enum RustError {
#[error("`cargo build` failed.\nstatus: {status}\nstdout: {stdout}\nstderr: {stderr}")]
CargoBuild {
status: ExitStatus,
stdout: String,
stderr: String,
},
#[error("failed to run `cargo build`: {0}")]
RustBuild(io::Error),
#[error("unable to create directory {0}:\n{1}")]
CreateDir(String, io::Error),
#[error(transparent)]
InstallRustLib(#[from] InstallRustLibError),
#[error(transparent)]
InstallLuaLib(#[from] InstallLuaLibError),
#[error(transparent)]
LuaVersionUnset(#[from] LuaVersionUnset),
}
#[derive(Error, Debug)]
#[error("error installing rust library {lib}:\n{cause}")]
pub struct InstallRustLibError {
lib: String,
cause: io::Error,
}
#[derive(Error, Debug)]
#[error("error installing Lua library {lib}:\n{cause}")]
pub struct InstallLuaLibError {
lib: String,
cause: io::Error,
}
impl BuildBackend for RustMluaBuildSpec {
type Err = RustError;
async fn run(self, args: RunBuildArgs<'_>) -> Result<BuildInfo, Self::Err> {
let output_paths = args.output_paths;
let config = args.config;
let build_dir = args.build_dir;
let progress = args.progress;
let lua_version = LuaVersion::from(config)?;
let lua_feature = match lua_version {
LuaVersion::Lua51 => "lua51",
LuaVersion::Lua52 => "lua52",
LuaVersion::Lua53 => "lua53",
LuaVersion::Lua54 => "lua54",
LuaVersion::Lua55 => "lua55",
LuaVersion::LuaJIT => "luajit",
LuaVersion::LuaJIT52 => "luajit",
};
let features = self
.features
.into_iter()
.chain(std::iter::once(lua_feature.into()))
.join(",");
let target_dir_arg = format!("--target-dir={}", self.target_path.display());
let mut build_args = vec!["build", "--release", &target_dir_arg];
if !self.default_features {
build_args.push("--no-default-features");
}
build_args.push("--features");
build_args.push(&features);
build_args.extend(self.cargo_extra_args.iter().map(|arg| arg.as_str()));
progress.map(|bar| bar.set_message("🛠️ Building (🦀 rust-mlua)..."));
match Command::new("cargo")
.current_dir(build_dir)
.args(build_args)
.output()
.await
{
Ok(output) if output.status.success() => {}
Ok(output) => {
return Err(RustError::CargoBuild {
status: output.status,
stdout: String::from_utf8_lossy(&output.stdout).into(),
stderr: String::from_utf8_lossy(&output.stderr).into(),
});
}
Err(err) => return Err(RustError::RustBuild(err)),
}
fs::create_dir_all(&output_paths.lib).map_err(|err| {
RustError::CreateDir(output_paths.lib.to_string_lossy().to_string(), err)
})?;
if let Err(err) =
install_rust_libs(self.modules, &self.target_path, build_dir, output_paths)
{
cleanup(output_paths, progress).await;
return Err(err.into());
}
fs::create_dir_all(&output_paths.src).map_err(|err| {
RustError::CreateDir(output_paths.src.to_string_lossy().to_string(), err)
})?;
if let Err(err) = install_lua_libs(self.include, build_dir, output_paths) {
cleanup(output_paths, progress).await;
return Err(err.into());
}
Ok(BuildInfo::default())
}
}
fn install_rust_libs(
modules: HashMap<String, PathBuf>,
target_path: &Path,
build_dir: &Path,
output_paths: &RockLayout,
) -> Result<(), InstallRustLibError> {
for (module, rust_lib) in modules {
let src = build_dir.join(target_path).join("release").join(rust_lib);
let mut dst: PathBuf = output_paths.lib.join(module);
dst.set_extension(c_dylib_extension());
fs::copy(&src, &dst).map_err(|err| InstallRustLibError {
lib: src.to_string_lossy().to_string(),
cause: err,
})?;
}
Ok(())
}
fn install_lua_libs(
include: HashMap<PathBuf, PathBuf>,
build_dir: &Path,
output_paths: &RockLayout,
) -> Result<(), InstallLuaLibError> {
for (from, to) in include {
let src = build_dir.join(from);
let dst = output_paths.src.join(to);
fs::copy(&src, &dst).map_err(|err| InstallLuaLibError {
lib: src.to_string_lossy().to_string(),
cause: err,
})?;
}
Ok(())
}
async fn cleanup(output_paths: &RockLayout, progress: &Progress<ProgressBar>) -> () {
let root_dir = &output_paths.rock_path;
progress.map(|p| p.set_message(format!("🗑️ Cleaning up {}", root_dir.display())));
match tokio::fs::remove_dir_all(root_dir).await {
Ok(_) => (),
Err(err) => {
progress
.map(|p| p.println(format!("Error cleaning up {}: {}", root_dir.display(), err)));
}
};
}