use std::{collections::HashMap, path::Path};
use itertools::Itertools;
use miette::Diagnostic;
use path_slash::PathExt;
use target_lexicon::Triple;
use thiserror::Error;
use crate::{
build::external_dependency::ExternalDependencyInfo,
config::Config,
fs,
lua_installation::LuaInstallation,
lua_rockspec::{LuaModule, ModulePaths},
variables::{self, Environment, VariableSubstitutionError},
};
use super::{c_link, CCExt};
#[derive(Error, Debug, Diagnostic)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
#[diagnostic(transparent)]
Fs(#[from] fs::Error),
#[error("failed to compile intermediates from C modules")]
#[diagnostic(help(
r#"check the compiler output above for missing headers or syntax errors.
ensure the C compiler has access to the required include paths."#
))]
CompileIntermediates(#[source] cc::Error),
#[error("error compiling C modules (compilation failed)")]
#[diagnostic(help(
r#"check the compiler output above for details.
if no C compiler was found, run `lx debug toolchains` to verify your build tools."#
))]
Compilation(#[from] cc::Error),
#[error("error compiling C modules (linking failed)")]
#[diagnostic(forward(0))]
Link(#[from] c_link::Error),
#[error(transparent)]
#[diagnostic(transparent)]
VariableSubstitution(#[from] VariableSubstitutionError),
}
#[tracing::instrument(
name = "Compiling C modules",
level = "info",
skip_all,
fields(profile = config.build_profile().to_string()),
)]
pub(crate) async fn compile(
data: &ModulePaths,
source_dir: &Path,
target_module: &LuaModule,
target_dir: &Path,
lua: &LuaInstallation,
external_dependencies: &HashMap<String, ExternalDependencyInfo>,
config: &Config,
) -> Result<(), Error> {
let target = target_dir.join(target_module.to_lib_path());
let target_parent_dir = target.parent().unwrap_or_else(|| {
unreachable!(
"couldn't determine build target parent directory:\n{}",
target.display()
)
});
fs::tokio::create_dir_all(target_parent_dir).await?;
let host = Triple::host();
let mut build = cc::Build::new();
let source_files = data
.sources
.iter()
.map(|dir| {
variables::substitute(
&[lua, external_dependencies, &Environment {}, config],
&dir.to_slash_lossy(),
)
})
.map(|dir| dir.map(|dir| source_dir.join(dir)))
.try_collect::<_, Vec<_>, _>()?;
let include_dirs = data
.incdirs
.iter()
.map(|dir| {
variables::substitute(
&[lua, external_dependencies, &Environment {}, config],
&dir.to_slash_lossy(),
)
})
.map(|dir| dir.map(|dir| source_dir.join(dir)))
.chain(
external_dependencies
.values()
.filter_map(|dep| dep.include_dir.clone())
.map(Result::Ok),
)
.try_collect::<_, Vec<_>, _>()?
.into_iter()
.unique() .collect_vec();
let intermediate_dir = fs::tempfile::tempdir()?;
let build = build
.cargo_output(config.verbose())
.cargo_debug(config.verbose())
.cargo_metadata(config.verbose())
.cargo_warnings(config.verbose())
.warnings(config.verbose())
.files(source_files)
.host(&host.to_string())
.target(&host.to_string())
.includes(&include_dirs)
.includes(lua.includes())
.includes(
external_dependencies
.values()
.filter_map(|dep| dep.include_dir.as_ref()),
)
.opt_level(config.build_profile().opt_level())
.out_dir(intermediate_dir);
let compiler = build.try_get_compiler()?;
let is_msvc = compiler.is_like_msvc();
if is_msvc {
build.flag("-W0");
} else {
build.flag("-w");
}
for arg in lua.define_flags() {
build.flag(&arg);
}
for (name, value) in &data.defines {
build.define(name, value.as_deref());
}
let target_file_name = target
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let objects = build
.try_compile_objects(config)
.map_err(Error::CompileIntermediates)?;
let libdir_args = data
.libdirs
.iter()
.map(|dir| {
variables::substitute(
&[lua, external_dependencies, &Environment {}, config],
&dir.to_slash_lossy(),
)
})
.map(|libdir| {
libdir.map(|libdir| {
if is_msvc {
format!("/LIBPATH:{}", source_dir.join(libdir).display())
} else {
format!("-L{}", source_dir.join(libdir).display())
}
})
})
.try_collect::<_, Vec<_>, _>()?;
let library_args = data
.libraries
.iter()
.map(|dir| {
variables::substitute(
&[lua, external_dependencies, &Environment {}, config],
&dir.to_slash_lossy(),
)
})
.map(|library| {
library.map(|library| {
if is_msvc {
format!("{}.lib", library)
} else {
format!("-l{}", library)
}
})
})
.try_collect::<_, Vec<_>, _>()?;
c_link::artifacts(
build,
lua,
external_dependencies,
&target_file_name,
libdir_args,
library_args,
target_module,
target_parent_dir,
objects,
config,
)
.await?;
Ok(())
}