1use std::io::Write;
9use std::path::PathBuf;
10use std::process::Command;
11use std::sync::atomic::{AtomicU64, Ordering};
12
13use tracing::{info, info_span};
14
15use gruel_util::{
16 CompileError, CompileErrors, CompileResult, CompileWarning, ErrorKind, MultiErrorResult,
17};
18
19use crate::{CompileOptions, CompileOutput};
20
21fn io_link_error(context: &str, err: std::io::Error) -> CompileError {
23 CompileError::without_span(ErrorKind::LinkError(format!("{}: {}", context, err)))
24}
25
26static TEMP_DIR_COUNTER: AtomicU64 = AtomicU64::new(0);
28
29static RUNTIME_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libgruel_runtime.a"));
32
33struct TempLinkDir {
39 path: PathBuf,
40 obj_paths: Vec<PathBuf>,
41 runtime_path: PathBuf,
42 output_path: PathBuf,
43}
44
45impl TempLinkDir {
46 fn new() -> CompileResult<Self> {
47 let unique_id = TEMP_DIR_COUNTER.fetch_add(1, Ordering::Relaxed);
48 let path = std::env::temp_dir().join(format!("gruel-{}-{}", std::process::id(), unique_id));
49 std::fs::create_dir_all(&path)
50 .map_err(|e| io_link_error("failed to create temp directory", e))?;
51
52 let runtime_path = path.join("libgruel_runtime.a");
53 let output_path = path.join("output");
54
55 Ok(Self {
56 path,
57 obj_paths: Vec::new(),
58 runtime_path,
59 output_path,
60 })
61 }
62
63 fn write_object_files(&mut self, object_files: &[Vec<u8>]) -> CompileResult<()> {
64 for (i, obj_bytes) in object_files.iter().enumerate() {
65 let obj_path = self.path.join(format!("obj{}.o", i));
66 let mut file = std::fs::File::create(&obj_path)
67 .map_err(|e| io_link_error("failed to create temp object file", e))?;
68 file.write_all(obj_bytes)
69 .map_err(|e| io_link_error("failed to write temp object file", e))?;
70 self.obj_paths.push(obj_path);
71 }
72 Ok(())
73 }
74
75 fn write_runtime(&self, runtime_bytes: &[u8]) -> CompileResult<()> {
76 std::fs::write(&self.runtime_path, runtime_bytes)
77 .map_err(|e| io_link_error("failed to write runtime archive", e))
78 }
79
80 fn read_output(&self) -> CompileResult<Vec<u8>> {
81 std::fs::read(&self.output_path)
82 .map_err(|e| io_link_error("failed to read linked executable", e))
83 }
84}
85
86impl Drop for TempLinkDir {
87 fn drop(&mut self) {
88 let _ = std::fs::remove_dir_all(&self.path);
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Default)]
97pub enum LinkerMode {
98 #[default]
100 Internal,
101 System(String),
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
110pub enum LinkMode {
111 #[default]
113 Dynamic,
114 Static,
119}
120
121pub(crate) fn link_system_with_warnings(
123 options: &CompileOptions,
124 object_files: &[Vec<u8>],
125 linker_cmd: &str,
126 warnings: &[CompileWarning],
127 extra_link_libraries: &[(String, LinkMode)],
128) -> MultiErrorResult<CompileOutput> {
129 let _span = info_span!("linker", mode = "system", command = linker_cmd).entered();
130
131 let mut temp_dir = TempLinkDir::new().map_err(CompileErrors::from)?;
132 temp_dir
133 .write_object_files(object_files)
134 .map_err(CompileErrors::from)?;
135 temp_dir
136 .write_runtime(RUNTIME_BYTES)
137 .map_err(CompileErrors::from)?;
138
139 let mut cmd = Command::new(linker_cmd);
140
141 if options.target.is_macho() {
146 cmd.arg("-nostartfiles");
147 cmd.arg("-arch").arg("arm64");
148 cmd.arg("-e").arg("__main");
149 } else {
150 cmd.arg("-nostartfiles");
151 }
152
153 cmd.arg("-o");
154 cmd.arg(&temp_dir.output_path);
155 for path in &temp_dir.obj_paths {
156 cmd.arg(path);
157 }
158 cmd.arg(&temp_dir.runtime_path);
159
160 if options.target.is_macho() {
161 cmd.arg("-lSystem");
162 }
163
164 let is_macho = options.target.is_macho();
169 let any_static = extra_link_libraries
170 .iter()
171 .any(|(_, mode)| *mode == LinkMode::Static);
172 let mut static_libs: Vec<&str> = Vec::new();
176 let mut dynamic_libs: Vec<&str> = Vec::new();
177 for (name, mode) in extra_link_libraries {
178 match mode {
179 LinkMode::Static => static_libs.push(name.as_str()),
180 LinkMode::Dynamic => dynamic_libs.push(name.as_str()),
181 }
182 }
183 if any_static {
184 if is_macho {
185 cmd.arg("-Wl,-search_paths_first");
189 for name in &static_libs {
190 cmd.arg(format!("-l{}", name));
191 }
192 } else {
193 cmd.arg("-Wl,-Bstatic");
194 for name in &static_libs {
195 cmd.arg(format!("-l{}", name));
196 }
197 cmd.arg("-Wl,-Bdynamic");
198 }
199 }
200 for name in &dynamic_libs {
201 cmd.arg(format!("-l{}", name));
202 }
203
204 let output = cmd.output().map_err(|e| {
205 CompileErrors::from(CompileError::without_span(ErrorKind::LinkError(format!(
206 "failed to execute linker '{}': {}",
207 linker_cmd, e
208 ))))
209 })?;
210
211 if !output.status.success() {
212 let stderr = String::from_utf8_lossy(&output.stderr);
213 return Err(CompileErrors::from(CompileError::without_span(
214 ErrorKind::LinkError(format!("linker '{}' failed: {}", linker_cmd, stderr)),
215 )));
216 }
217
218 let elf = temp_dir.read_output().map_err(CompileErrors::from)?;
219 info!(
220 object_count = object_files.len(),
221 output_bytes = elf.len(),
222 "linking complete"
223 );
224
225 Ok(CompileOutput {
226 elf,
227 warnings: warnings.to_vec(),
228 })
229}