Skip to main content

lux_lib/build/compile/
c_files.rs

1use std::{
2    collections::HashMap,
3    path::{Path, PathBuf},
4};
5
6use miette::Diagnostic;
7use target_lexicon::Triple;
8use thiserror::Error;
9
10use crate::{
11    build::external_dependency::ExternalDependencyInfo, config::Config, fs,
12    lua_installation::LuaInstallation, lua_rockspec::LuaModule,
13};
14
15use super::{c_link, CCExt};
16
17#[derive(Error, Debug, Diagnostic)]
18#[non_exhaustive]
19pub enum Error {
20    #[error(transparent)]
21    #[diagnostic(transparent)]
22    Fs(#[from] fs::Error),
23    #[error("failed to compile intermediates from C files")]
24    #[diagnostic(help(
25        r#"check the compiler output above for missing headers or syntax errors.
26ensure the C compiler has access to the required include paths."#
27    ))]
28    CompileIntermediates { source: cc::Error },
29    #[error("error compiling C files (compilation failed):")]
30    #[diagnostic(help(
31        r#"check the compiler output above for details.
32if no C compiler was found, run `lx debug toolchains` to verify your build tools."#
33    ))]
34    Compilation(#[from] cc::Error),
35    #[error("error compiling C files (linking failed)")]
36    #[diagnostic(forward(0))]
37    Link(#[from] c_link::Error),
38}
39
40/// Compiles a set of C files into a single dynamic library and places them under `{target_dir}/{target_file}`.
41/// # Panics
42/// Panics if no parent or no filename can be determined for the target path.
43#[tracing::instrument(
44    name = "Compiling C files",
45    level = "info",
46    skip_all,
47    fields(profile = config.build_profile().to_string()),
48)]
49pub(crate) async fn compile(
50    files: &Vec<PathBuf>,
51    target_module: &LuaModule,
52    target_dir: &Path,
53    lua: &LuaInstallation,
54    external_dependencies: &HashMap<String, ExternalDependencyInfo>,
55    config: &Config,
56) -> Result<(), Error> {
57    let target = target_dir.join(target_module.to_lib_path());
58    let target_parent_dir = target.parent().unwrap_or_else(|| {
59        unreachable!(
60            "couldn't determine build target parent directory:\n{}",
61            target.display()
62        )
63    });
64    let target_file_name = target
65        .file_name()
66        .unwrap_or_default()
67        .to_string_lossy()
68        .to_string();
69
70    fs::sync::create_dir_all(target_parent_dir)?;
71
72    let host = Triple::host();
73
74    // See https://github.com/rust-lang/cc-rs/issues/594#issuecomment-2110551057
75
76    let mut build = cc::Build::new();
77    let intermediate_dir = fs::tempfile::tempdir()?;
78    let build = build
79        .cargo_output(config.verbose())
80        .cargo_debug(config.verbose())
81        .cargo_metadata(config.verbose())
82        .cargo_warnings(config.verbose())
83        .warnings(config.verbose())
84        .files(files)
85        .host(&host.to_string())
86        .target(&host.to_string())
87        .includes(lua.includes())
88        .includes(
89            external_dependencies
90                .values()
91                .filter_map(|dep| dep.include_dir.as_ref()),
92        )
93        .opt_level(config.build_profile().opt_level())
94        .out_dir(intermediate_dir);
95
96    let compiler = build.try_get_compiler()?;
97    // Suppress all warnings
98    if compiler.is_like_msvc() {
99        build.flag("-W0");
100    } else {
101        build.flag("-w");
102    }
103    for arg in lua.define_flags() {
104        build.flag(&arg);
105    }
106
107    let objects = build
108        .try_compile_objects(config)
109        .map_err(|source| Error::CompileIntermediates { source })?;
110
111    c_link::artifacts(
112        build,
113        lua,
114        external_dependencies,
115        &target_file_name,
116        Vec::new(),
117        Vec::new(),
118        target_module,
119        target_parent_dir,
120        objects,
121        config,
122    )
123    .await?;
124    Ok(())
125}