lux_lib/build/compile/
c_link.rs1use std::{
2 collections::HashMap,
3 io,
4 path::{Path, PathBuf},
5 process::{ExitStatus, Output},
6};
7
8use itertools::Itertools;
9use miette::Diagnostic;
10use path_slash::PathExt;
11use thiserror::Error;
12use tokio::process::Command;
13
14use crate::{
15 build::external_dependency::ExternalDependencyInfo, config::Config, fs,
16 lua_installation::LuaInstallation, lua_rockspec::LuaModule,
17};
18
19#[derive(Error, Debug, Diagnostic)]
20#[non_exhaustive]
21pub enum Error {
22 #[error("IO operation failed while linking C modules")]
23 Io(#[from] io::Error),
24 #[error(transparent)]
25 #[diagnostic(transparent)]
26 Fs(#[from] fs::Error),
27 #[error(transparent)]
28 CC(#[from] cc::Error),
29 #[error("compilation failed.\nstatus: {status}\nstdout: {stdout}\nstderr: {stderr}")]
30 #[diagnostic(help(
31 r#"check the compiler output above for details.
32rerun with `--verbose` for more information."#
33 ))]
34 CommandFailure {
35 status: ExitStatus,
36 stdout: String,
37 stderr: String,
38 },
39 #[error("compiling C modules succeeded, but the expected library {0} was not created")]
40 #[diagnostic(help(
41 "check the rockspec or lux.toml `modules` and `lib_modules` tables for correct paths."
42 ))]
43 LibOutputNotCreated(String),
44}
45
46fn validate_output(output: &Output) -> Result<(), Error> {
47 if !output.status.success() {
48 return Err(Error::CommandFailure {
49 status: output.status,
50 stdout: String::from_utf8_lossy(&output.stdout).into(),
51 stderr: String::from_utf8_lossy(&output.stderr).into(),
52 });
53 }
54 Ok(())
55}
56
57#[tracing::instrument(level = "trace")]
59fn mk_def_file(
60 dir: &Path,
61 output_file_name: &str,
62 target_module: &LuaModule,
63) -> Result<PathBuf, fs::Error> {
64 let mut def_file: PathBuf = dir.join(output_file_name);
65 def_file.set_extension(".def");
66 let exported_name = target_module.to_string().replace(".", "_");
67 let exported_name = exported_name
68 .split_once('-')
69 .map(|(_, after_hyphen)| after_hyphen.to_string())
70 .unwrap_or_else(|| exported_name.clone());
71 let content = format!(
72 r#"EXPORTS
73luaopen_{exported_name}
74"#,
75 );
76 fs::sync::write(&def_file, content)?;
77 Ok(def_file)
78}
79
80#[allow(clippy::too_many_arguments)]
81pub(crate) async fn artifacts(
82 build: &mut cc::Build,
83 lua: &LuaInstallation,
84 external_dependencies: &HashMap<String, ExternalDependencyInfo>,
85 target_file_name: &str,
86 libdir_args: Vec<String>,
87 library_args: Vec<String>,
88 target_module: &LuaModule,
89 target_parent_dir: &Path,
90 objects: Vec<PathBuf>,
91 config: &Config,
92) -> Result<(), Error> {
93 let output_path =
94 std::path::absolute(target_parent_dir.join(target_file_name)).map_err(Error::Io)?;
95 let compiler = build.try_get_compiler()?;
96 let temp_work_dir = fs::tempfile::tempdir()?;
100 let is_msvc = compiler.is_like_msvc();
101 let cmd = build.try_get_compiler()?.to_command();
102 let mut cmd: tokio::process::Command = cmd.into();
103 cmd.current_dir(temp_work_dir.path());
104 let output = if is_msvc {
105 let def_file = mk_def_file(temp_work_dir.path(), target_file_name, target_module)?;
106 cmd.args(&objects).arg("/LD");
107 add_variable_if_set(config, "LDFLAGS", &mut cmd);
108 cmd.arg("/link")
109 .arg("/NOIMPLIB")
110 .arg("/NOEXP")
111 .arg(format!("/DEF:{}", def_file.display()))
112 .arg(format!("/OUT:{}", output_path.display()))
113 .args(lua_lib_link_args(lua, &compiler))
114 .args(
115 external_dependencies
116 .values()
117 .flat_map(|dep| dep.lib_link_args(&compiler)),
118 )
119 .args(libdir_args)
120 .args(library_args)
121 .output()
122 .await?
123 } else {
124 add_variable_if_set(config, "LIBFLAG", &mut cmd);
125 add_variable_if_set(config, "LDFLAGS", &mut cmd);
126 cmd.args(vec!["-o".into(), output_path.to_string_lossy().to_string()])
127 .args(lua_lib_link_args(lua, &build.try_get_compiler()?))
128 .args(
129 external_dependencies
130 .values()
131 .flat_map(|dep| dep.lib_link_args(&compiler)),
132 )
133 .args(&objects)
134 .args(libdir_args)
135 .args(library_args)
136 .output()
137 .await?
138 };
139 crate::build::command::trace_output(&output);
140 validate_output(&output)?;
141
142 if output_path.exists() {
143 Ok(())
144 } else {
145 Err(Error::LibOutputNotCreated(
146 output_path.to_slash_lossy().to_string(),
147 ))
148 }
149}
150
151fn lua_lib_link_args(lua: &LuaInstallation, compiler: &cc::Tool) -> Vec<String> {
153 if cfg!(target_os = "macos") {
154 Vec::new()
156 } else {
157 lua.lib_link_args(compiler)
158 }
159}
160
161fn add_variable_if_set(config: &Config, name: &str, cmd: &mut Command) {
164 let variables = config.variables();
165 if let Some(var_str) = variables.get(name) {
166 if !var_str.is_empty() {
167 let vars = var_str.split_whitespace().collect_vec();
168 cmd.args(vars);
169 }
170 }
171}