cargo_hyperlight/
toolchain.rs1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result, ensure};
4use proc_macro2::TokenStream;
5use quote::{TokenStreamExt, quote};
6use regex::Regex;
7
8use crate::cargo_cmd::{CargoCmd, cargo_cmd};
9use crate::cli::Args;
10use crate::sysroot::CargoBuildMessage;
11use crate::{toolchain_flags, util};
12
13#[derive(serde::Deserialize)]
14struct CargoMetadata {
15 packages: Vec<CargoMetadataPackage>,
16}
17
18#[derive(serde::Deserialize)]
19struct CargoMetadataPackage {
20 name: String,
21 manifest_path: PathBuf,
22 #[allow(dead_code)]
23 version: semver::Version,
25}
26
27struct PackageDirectories {
28 hyperlight_libc: Option<PathBuf>,
29 hyperlight_guest_bin: Option<PathBuf>,
30 hyperlight_guest_capi: Option<PathBuf>,
31}
32impl PackageDirectories {
33 fn libc(&self) -> Result<PathBuf> {
34 self.hyperlight_libc
35 .as_ref()
36 .or(self.hyperlight_guest_bin.as_ref())
37 .cloned()
38 .context(
39 "Could not find hyperlight-libc or hyperlight-guest-bin package in cargo metadata",
40 )
41 }
42 fn guest_capi(&self) -> Result<PathBuf> {
43 self.hyperlight_guest_capi
44 .clone()
45 .context("Could not find hyperlight-guest-capi package in cargo metadata")
46 }
47}
48
49fn find_package_dir(metadata: &CargoMetadata, name: &str) -> Result<Option<PathBuf>> {
50 metadata
51 .packages
52 .iter()
53 .find(|x| x.name == name)
54 .map(|pkg| {
55 pkg.manifest_path
56 .parent()
57 .with_context(|| format!("Failed to get directory for {name}"))
58 .map(|x| x.to_path_buf())
59 })
60 .transpose()
61}
62
63fn find_package_dirs(args: &Args) -> Result<PackageDirectories> {
64 let metadata = cargo_cmd()?
65 .env_clear()
66 .envs(args.env.iter())
67 .current_dir(&args.current_dir)
68 .arg("metadata")
69 .manifest_path(&args.manifest_path)
70 .arg("--format-version=1")
71 .append_rustflags("--cfg=hyperlight")
72 .append_rustflags("--check-cfg=cfg(hyperlight)")
73 .checked_output()
74 .context("Failed to get cargo metadata")?;
75
76 let metadata = serde_json::from_slice::<CargoMetadata>(&metadata.stdout)
77 .context("Failed to parse cargo metadata")?;
78
79 Ok(PackageDirectories {
80 hyperlight_libc: find_package_dir(&metadata, "hyperlight-libc")?,
81 hyperlight_guest_bin: find_package_dir(&metadata, "hyperlight-guest-bin")?,
82 hyperlight_guest_capi: if args.with_guest_capi {
83 find_package_dir(&metadata, "hyperlight_guest_capi")?
84 } else {
85 None
86 },
87 })
88}
89
90fn copy_includes(src_dirs: impl Iterator<Item: AsRef<Path>>, dst_dir: &Path) -> Result<()> {
91 util::union_glob(src_dirs, dst_dir, "**/*.h")
92}
93
94fn build_guest_capi(args: &Args, capi_dir: &Path) -> Result<()> {
95 use crate::CargoCommandExt;
96 let output = cargo_cmd()?
97 .env_clear()
98 .envs(args.env.iter())
99 .arg("build")
100 .manifest_path(&Some(capi_dir.join("Cargo.toml")))
101 .target_dir(args.build_dir())
102 .arg("--message-format=json")
103 .env_remove("RUSTC_WORKSPACE_WRAPPER")
104 .populate_from_args(args, true)
105 .output()
106 .context("Failed to build capi cargo project")?;
107 ensure!(
108 output.status.success(),
109 "Failed to build capi\n{}",
110 String::from_utf8_lossy(&output.stderr)
111 );
112
113 let messages = String::from_utf8_lossy(&output.stdout);
114
115 for message in messages.lines() {
116 let message = serde_json::from_str::<CargoBuildMessage>(message)
117 .context("Failed to parse sysroot build message")?;
118 if message.reason == "compiler-artifact" {
119 let name = message.target.name;
120 if name == "hyperlight_guest_capi" {
121 for file in message.filenames {
122 let file_name = file.file_name().with_context(|| {
123 format!(
124 "Failed to get filename for capi build artifact {}",
125 file.display()
126 )
127 })?;
128 let dst = args.c_libs_dir().join(file_name);
129 std::fs::copy(&file, &dst)?;
130 }
131 }
132 }
133 }
134
135 Ok(())
136}
137
138fn path_to_tokens(p: &Path) -> TokenStream {
139 let mut tokens = quote! {
140 let mut x: ::std::path::PathBuf = ::std::path::PathBuf::new();
141 };
142 for x in p.iter() {
143 let s = x.to_string_lossy();
144 tokens.append_all(quote! {
145 x.push(#s);
146 });
147 }
148 tokens.append_all(quote! { x });
149 quote! { { #tokens } }
150}
151
152fn build_wrappers(args: &Args) -> Result<()> {
153 const CARGO_TOML: &str = include_str!("wrapper/_Cargo.toml");
154 const MAIN_RS: &str = include_str!("wrapper/_main.rs");
155 const CLANG_PARSER_RS: &str = include_str!("wrapper/_clang_parser.rs");
156 const FLAGS_RS: &str = include_str!("toolchain_flags.rs");
157
158 let wrapper_src_dir = args.wrapper_src_dir();
159 std::fs::create_dir_all(&wrapper_src_dir)
160 .context("Failed to create wrapper source directory")?;
161 std::fs::write(wrapper_src_dir.join("Cargo.toml"), CARGO_TOML)?;
162 let wrapper_src_src_dir = wrapper_src_dir.join("src");
163 std::fs::create_dir_all(&wrapper_src_src_dir)
164 .context("Failed to create wrapper source src directory")?;
165 std::fs::write(wrapper_src_src_dir.join("main.rs"), MAIN_RS)?;
166 std::fs::write(wrapper_src_src_dir.join("clang_parser.rs"), CLANG_PARSER_RS)?;
167 std::fs::write(wrapper_src_src_dir.join("toolchain_flags.rs"), FLAGS_RS)?;
168 let includes_toks = path_to_tokens(args.includes_dir().strip_prefix(args.sysroot_dir())?);
169 let c_libs_toks = path_to_tokens(args.c_libs_dir().strip_prefix(args.sysroot_dir())?);
170 let wrapper_toks = path_to_tokens(args.wrapper_dir().strip_prefix(args.sysroot_dir())?);
171 let target = &args.target;
172 let with_guest_capi = args.with_guest_capi;
173 std::fs::write(
174 wrapper_src_src_dir.join("args.rs"),
175 (quote! {
176 pub(crate) fn args(root: &std::path::Path) -> crate::toolchain_flags::Args {
177 crate::toolchain_flags::Args {
178 includes_dir: root.join(#includes_toks),
179 c_libs_dir: root.join(#c_libs_toks),
180 wrapper_dir: root.join(#wrapper_toks),
181 target: #target.to_string(),
182 with_guest_capi: #with_guest_capi,
183 }
184 }
185 })
186 .to_string(),
187 )?;
188
189 let output = cargo_cmd()?
190 .env_clear()
191 .envs(args.env.iter())
192 .current_dir(&args.current_dir)
193 .arg("build")
194 .target(&args.host)
195 .manifest_path(&Some(wrapper_src_dir.join("Cargo.toml")))
196 .target_dir(args.build_dir())
197 .arg("--release")
198 .arg("--message-format=json")
199 .env_remove("RUSTC_WORKSPACE_WRAPPER")
200 .output()
201 .context("Failed to build wrapper cargo project")?;
202 ensure!(
203 output.status.success(),
204 "Failed to build wrapper\n{}",
205 String::from_utf8_lossy(&output.stderr)
206 );
207
208 let messages = String::from_utf8_lossy(&output.stdout);
209
210 for message in messages.lines() {
211 let message = serde_json::from_str::<CargoBuildMessage>(message)
212 .context("Failed to parse wrapper build message")?;
213 if message.reason == "compiler-artifact" {
214 let name = message.target.name;
215 if name == "hyperlight-sysroot-wrappers" {
216 let files: Vec<_> = message
217 .filenames
218 .iter()
219 .filter(|x| x.extension() != Some("pdb".as_ref()))
220 .collect();
221 ensure!(
222 files.len() == 1,
223 "hyperlight-sysroot-wrappers produced wrong number of binaries",
224 );
225 let target_uses_exe = message.filenames[0].extension() == Some("exe".as_ref());
226 let dir = args.wrapper_dir();
227 let bin_name = |n| {
228 let mut p = dir.join(n);
229 if target_uses_exe {
230 p.set_extension("exe");
231 }
232 p
233 };
234 std::fs::create_dir_all(&dir).context("Failed to create wrapper bin directory")?;
235 std::fs::copy(&message.filenames[0], bin_name("hyperlight-config"))?;
236 std::fs::copy(&message.filenames[0], bin_name("clang"))?;
237 std::fs::copy(
238 &message.filenames[0],
239 bin_name(&format!("{}-clang", target)),
240 )?;
241 }
242 }
243 }
244 Ok(())
245}
246
247pub fn prepare(args: &Args) -> Result<()> {
248 let package_dirs = find_package_dirs(args)?;
249 let libc_dir = package_dirs.libc()?;
250
251 let include_dst_dir = args.includes_dir();
252
253 std::fs::create_dir_all(&include_dst_dir)
254 .context("Failed to create sysroot include directory")?;
255
256 let mut include_dirs: Vec<&str> = vec![
258 "third_party/printf/",
260 "third_party/musl/include",
261 "third_party/musl/arch/generic",
262 "third_party/musl/src/internal",
263 "third_party/picolibc/libc/include",
265 "third_party/picolibc/libc/stdio",
266 "include",
267 ];
268 if !args.target.starts_with("aarch64") {
269 include_dirs.push("third_party/musl/arch/x86_64");
270 }
271
272 let capi_dir = args
273 .with_guest_capi
274 .then(|| {
275 let d = package_dirs.guest_capi()?;
276 build_guest_capi(args, &d)?;
277 Ok::<_, anyhow::Error>(d)
278 })
279 .transpose()?;
280
281 let include_dirs = include_dirs
282 .into_iter()
283 .map(|dir| libc_dir.join(dir))
284 .chain(capi_dir.map(|x| x.join("include")));
285 copy_includes(include_dirs, &include_dst_dir)?;
286
287 build_wrappers(args)?;
288
289 Ok(())
290}
291
292impl From<&Args> for toolchain_flags::Args {
293 fn from(args: &Args) -> toolchain_flags::Args {
294 toolchain_flags::Args {
295 includes_dir: args.includes_dir(),
296 c_libs_dir: args.c_libs_dir(),
297 wrapper_dir: args.wrapper_dir(),
298 target: args.target.clone(),
299 with_guest_capi: args.with_guest_capi,
300 }
301 }
302}
303
304pub fn cflags(args: &Args, bootstrap: bool) -> toolchain_flags::Flags {
305 toolchain_flags::cflags(&args.into(), bootstrap)
306}
307pub fn ldflags(args: &Args) -> toolchain_flags::Flags {
308 toolchain_flags::ldflags(&args.into())
309}
310pub fn libs(args: &Args) -> toolchain_flags::Flags {
311 toolchain_flags::libs(&args.into())
312}
313
314pub fn find_cc() -> Result<PathBuf> {
315 if let Ok(path) = which::which("clang") {
316 return Ok(path);
317 }
318 let re = Regex::new(r"clang-\d+").unwrap();
320 which::which_re(&re)
321 .context("Could not find 'clang' in PATH")?
322 .next()
323 .context("Could not find 'clang' in PATH")
324}
325
326pub fn find_ar() -> Result<PathBuf> {
327 #[cfg(not(target_os = "macos"))]
328 let ar = which::which("ar");
329 let llvm_ar = which::which("llvm-ar");
330 #[cfg(target_os = "macos")]
335 let preferred_ar = llvm_ar.or(ar);
336 #[cfg(not(target_os = "macos"))]
337 let preferred_ar = ar.or(llvm_ar);
338 if let Ok(ar) = preferred_ar {
339 return Ok(ar);
340 }
341
342 let re = Regex::new(r"llvm-ar-\d+").unwrap();
344 which::which_re(&re)
345 .context("Could not find 'ar' or 'llvm-ar' in PATH")?
346 .next()
347 .context("Could not find 'ar' or 'llvm-ar' in PATH")
348}