lux_lib/build/compile/
c_modules.rs1use std::{collections::HashMap, path::Path};
2
3use itertools::Itertools;
4use miette::Diagnostic;
5use path_slash::PathExt;
6use target_lexicon::Triple;
7use thiserror::Error;
8
9use crate::{
10 build::external_dependency::ExternalDependencyInfo,
11 config::Config,
12 fs,
13 lua_installation::LuaInstallation,
14 lua_rockspec::{LuaModule, ModulePaths},
15 variables::{self, Environment, VariableSubstitutionError},
16};
17
18use super::{c_link, CCExt};
19
20#[derive(Error, Debug, Diagnostic)]
23#[non_exhaustive]
24pub enum Error {
25 #[error(transparent)]
26 #[diagnostic(transparent)]
27 Fs(#[from] fs::Error),
28 #[error("failed to compile intermediates from C modules")]
29 #[diagnostic(help(
30 r#"check the compiler output above for missing headers or syntax errors.
31ensure the C compiler has access to the required include paths."#
32 ))]
33 CompileIntermediates(#[source] cc::Error),
34 #[error("error compiling C modules (compilation failed)")]
35 #[diagnostic(help(
36 r#"check the compiler output above for details.
37if no C compiler was found, run `lx debug toolchains` to verify your build tools."#
38 ))]
39 Compilation(#[from] cc::Error),
40 #[error("error compiling C modules (linking failed)")]
41 #[diagnostic(forward(0))]
42 Link(#[from] c_link::Error),
43 #[error(transparent)]
44 #[diagnostic(transparent)]
45 VariableSubstitution(#[from] VariableSubstitutionError),
46}
47
48#[tracing::instrument(
52 name = "Compiling C modules",
53 level = "info",
54 skip_all,
55 fields(profile = config.build_profile().to_string()),
56)]
57pub(crate) async fn compile(
58 data: &ModulePaths,
59 source_dir: &Path,
60 target_module: &LuaModule,
61 target_dir: &Path,
62 lua: &LuaInstallation,
63 external_dependencies: &HashMap<String, ExternalDependencyInfo>,
64 config: &Config,
65) -> Result<(), Error> {
66 let target = target_dir.join(target_module.to_lib_path());
67
68 let target_parent_dir = target.parent().unwrap_or_else(|| {
69 unreachable!(
70 "couldn't determine build target parent directory:\n{}",
71 target.display()
72 )
73 });
74
75 fs::tokio::create_dir_all(target_parent_dir).await?;
76
77 let host = Triple::host();
78
79 let mut build = cc::Build::new();
80 let source_files = data
81 .sources
82 .iter()
83 .map(|dir| {
84 variables::substitute(
85 &[lua, external_dependencies, &Environment {}, config],
86 &dir.to_slash_lossy(),
87 )
88 })
89 .map(|dir| dir.map(|dir| source_dir.join(dir)))
90 .try_collect::<_, Vec<_>, _>()?;
91 let include_dirs = data
92 .incdirs
93 .iter()
94 .map(|dir| {
95 variables::substitute(
96 &[lua, external_dependencies, &Environment {}, config],
97 &dir.to_slash_lossy(),
98 )
99 })
100 .map(|dir| dir.map(|dir| source_dir.join(dir)))
101 .chain(
102 external_dependencies
103 .values()
104 .filter_map(|dep| dep.include_dir.clone())
105 .map(Result::Ok),
106 )
107 .try_collect::<_, Vec<_>, _>()?
108 .into_iter()
109 .unique() .collect_vec();
111
112 let intermediate_dir = fs::tempfile::tempdir()?;
113 let build = build
114 .cargo_output(config.verbose())
115 .cargo_debug(config.verbose())
116 .cargo_metadata(config.verbose())
117 .cargo_warnings(config.verbose())
118 .warnings(config.verbose())
119 .files(source_files)
120 .host(&host.to_string())
121 .target(&host.to_string())
122 .includes(&include_dirs)
123 .includes(lua.includes())
124 .includes(
125 external_dependencies
126 .values()
127 .filter_map(|dep| dep.include_dir.as_ref()),
128 )
129 .opt_level(config.build_profile().opt_level())
130 .out_dir(intermediate_dir);
131
132 let compiler = build.try_get_compiler()?;
133 let is_msvc = compiler.is_like_msvc();
134 if is_msvc {
136 build.flag("-W0");
137 } else {
138 build.flag("-w");
139 }
140 for arg in lua.define_flags() {
141 build.flag(&arg);
142 }
143
144 for (name, value) in &data.defines {
147 build.define(name, value.as_deref());
148 }
149
150 let target_file_name = target
151 .file_name()
152 .unwrap_or_default()
153 .to_string_lossy()
154 .to_string();
155 let objects = build
157 .try_compile_objects(config)
158 .map_err(Error::CompileIntermediates)?;
159
160 let libdir_args = data
161 .libdirs
162 .iter()
163 .map(|dir| {
164 variables::substitute(
165 &[lua, external_dependencies, &Environment {}, config],
166 &dir.to_slash_lossy(),
167 )
168 })
169 .map(|libdir| {
170 libdir.map(|libdir| {
171 if is_msvc {
172 format!("/LIBPATH:{}", source_dir.join(libdir).display())
173 } else {
174 format!("-L{}", source_dir.join(libdir).display())
175 }
176 })
177 })
178 .try_collect::<_, Vec<_>, _>()?;
179
180 let library_args = data
181 .libraries
182 .iter()
183 .map(|dir| {
184 variables::substitute(
185 &[lua, external_dependencies, &Environment {}, config],
186 &dir.to_slash_lossy(),
187 )
188 })
189 .map(|library| {
190 library.map(|library| {
191 if is_msvc {
192 format!("{}.lib", library)
193 } else {
194 format!("-l{}", library)
195 }
196 })
197 })
198 .try_collect::<_, Vec<_>, _>()?;
199
200 c_link::artifacts(
201 build,
202 lua,
203 external_dependencies,
204 &target_file_name,
205 libdir_args,
206 library_args,
207 target_module,
208 target_parent_dir,
209 objects,
210 config,
211 )
212 .await?;
213 Ok(())
214}