cargo_sysroot/lib.rs
1//! # Cargo-Sysroot
2//!
3//! Compiles the Rust sysroot crates, core, compiler_builtins, and alloc.
4use anyhow::{anyhow, Context, Error, Result};
5use cargo_toml2::{
6 from_path,
7 to_path,
8 CargoToml,
9 Dependency,
10 DependencyFull,
11 Package,
12 Patches,
13 TargetConfig,
14 Workspace,
15};
16use std::{
17 collections::BTreeMap,
18 env,
19 ffi::OsString,
20 fs,
21 path::{Path, PathBuf},
22 process::Command,
23};
24
25mod util;
26
27pub use util::get_rust_src;
28
29/// The sysroot crates to build.
30///
31/// See [`build_sysroot_with`] for details.
32#[derive(Debug, Copy, Clone)]
33pub enum Sysroot {
34 /// The core crate. Provides core functionality.
35 ///
36 /// This does **not** include [`Sysroot::CompilerBuiltins`],
37 /// which is what you probably want unless your target
38 /// needs special handling.
39 Core,
40
41 /// Compiler-builtins crate.
42 ///
43 /// This implies [`Sysroot::Core`].
44 CompilerBuiltins,
45
46 /// The alloc crate. Gives you a heap, and things to put on it.
47 ///
48 /// This implies [`Sysroot::Core`], and [`Sysroot::CompilerBuiltins`].
49 Alloc,
50
51 /// The standard library. Gives you an operating system.
52 ///
53 /// This implies [`Sysroot::Alloc`], [`Sysroot::Core`], and
54 /// [`Sysroot::CompilerBuiltins`].
55 Std,
56}
57
58/// Features to enable when building the sysroot crates
59///
60/// See [`SysrootBuilder::features`] for usage.
61#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
62pub enum Features {
63 /// This enables the `mem` feature of [`compiler_builtins`][1],
64 /// which will provide memory related intrinsics such as `memcpy`.
65 ///
66 /// [1]: https://github.com/rust-lang/compiler-builtins
67 CompilerBuiltinsMem,
68
69 /// This enables the `c` feature of [`compiler_builtins`][1],
70 /// which enables compilation of `C` code and may result in more
71 /// optimized implementations, and fills in the rare unimplemented
72 /// intrinsics.
73 ///
74 /// [1]: https://github.com/rust-lang/compiler-builtins
75 CompilerBuiltinsC,
76
77 /// This enables the `no-asm` feature of [`compiler_builtins`][1],
78 /// which disables any implementations which use
79 /// inline assembly and fall back to pure Rust versions (if available).
80 ///
81 /// [1]: https://github.com/rust-lang/compiler-builtins
82 // TODO: Would only work on the [`Sysroot::CompilerBuiltins`] target,
83 // as it's not exported through alloc, but could be forced by
84 // adding compiler_builtins as an explicit dependency and enabling it,
85 // relying on features collapsing.
86 CompilerBuiltinsNoAsm,
87}
88
89/// A builder interface for constructing the Sysroot
90///
91/// See the individual methods for more details on what this means
92/// and what defaults exist.
93#[derive(Debug, Clone)]
94pub struct SysrootBuilder {
95 /// Manifest to use for cargo profiles
96 manifest: Option<PathBuf>,
97
98 /// Output directory, where the built sysroot will be anchored.
99 output: PathBuf,
100
101 /// Target triple/json to build for
102 target: Option<PathBuf>,
103
104 /// The rust sources to use
105 rust_src: Option<PathBuf>,
106
107 /// Which crates to include in the sysroot
108 sysroot_crate: Sysroot,
109
110 /// What custom features to enable, if any. See [`Features`] for details.
111 features: Vec<Features>,
112
113 /// Custom flags to pass to rustc.
114 rustc_flags: Vec<OsString>,
115}
116
117impl SysrootBuilder {
118 /// New [`SysrootBuilder`].
119 ///
120 /// `sysroot_crate` specifies which libraries to build as part of
121 /// the sysroot. See [`Sysroot`] for more details.
122 pub fn new(sysroot_crate: Sysroot) -> Self {
123 Self {
124 manifest: Default::default(),
125 output: PathBuf::from(".").join("target").join("sysroot"),
126 target: Default::default(),
127 // Set in [`SysrootBuilder::build`] since `new` can't error.
128 rust_src: Default::default(),
129 sysroot_crate,
130 features: Vec::with_capacity(3),
131 rustc_flags: Default::default(),
132 }
133 }
134
135 /// Set path to the `Cargo.toml` of the project requiring a custom sysroot.
136 ///
137 /// If provided, any [Cargo Profile's][1] in the provided manifest
138 /// will be copied into the sysroot crate being compiled.
139 ///
140 /// If not provided, profiles use their default settings.
141 ///
142 /// By default this will be `None`.
143 ///
144 /// [1]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html
145 pub fn manifest(&mut self, manifest: PathBuf) -> &mut Self {
146 self.manifest = Some(manifest);
147 self
148 }
149
150 /// Set where the sysroot directory will be placed.
151 ///
152 /// By default this is `./target/sysroot`.
153 pub fn output(&mut self, output: PathBuf) -> &mut Self {
154 self.output = output;
155 self
156 }
157
158 /// The target to compile *for*. This can be a target-triple,
159 /// or a [JSON Target Specification][1].
160 ///
161 /// By default this is `None`, and if not set when
162 /// [`SysrootBuilder::build`] is called, will cause an error.
163 ///
164 /// [1]: https://doc.rust-lang.org/rustc/targets/custom.html
165 pub fn target(&mut self, target: PathBuf) -> &mut Self {
166 self.target = Some(target);
167 self
168 }
169
170 /// The rust source directory. These are used to compile the sysroot.
171 ///
172 /// By default this uses the `rust-src` component from the
173 /// current `rustup` toolchain.
174 pub fn rust_src(&mut self, rust_src: PathBuf) -> &mut Self {
175 self.rust_src = Some(rust_src);
176 self
177 }
178
179 /// Which features to enable.
180 ///
181 /// This *adds* to, not *replaces*, any previous calls to this method.
182 ///
183 /// By default this is empty.
184 ///
185 /// See [`Features`] for details.
186 pub fn features(&mut self, features: &[Features]) -> &mut Self {
187 self.features.extend_from_slice(features);
188 // TODO: Should?? Not??
189 self.features.sort_unstable();
190 self.features.dedup();
191 self
192 }
193
194 /// Custom flags to pass to **all** `rustc` compiler invocations.
195 ///
196 /// This *adds* to, not *replaces*, any previous calls to this method.
197 ///
198 /// By default this is empty.
199 ///
200 /// # Internal
201 ///
202 /// This will use the `RUSTFLAGS` to ensure flags are set for all
203 /// invocations.
204 ///
205 /// Flags passed to this method will be *appended*
206 /// to any existing `RUSTFLAGS`.
207 pub fn rustc_flags<I, S>(&mut self, flags: I) -> &mut Self
208 where
209 I: IntoIterator<Item = S>,
210 S: Into<OsString>,
211 {
212 self.rustc_flags.extend(flags.into_iter().map(Into::into));
213 self
214 }
215
216 /// Build the Sysroot, and return a path suitable to pass to rustc.
217 ///
218 /// # Errors
219 ///
220 /// - [`SysrootBuilder::target`] was not called
221 /// - If `manifest` is provided and does not exist
222 /// - If `target` is a JSON specification, but doesn't exist.
223 /// - If the `rust_src` directory does not exist, or could not be detected.
224 /// - If the sysroot cannot be setup, or fails to compile
225 pub fn build(&self) -> Result<PathBuf> {
226 let target = match &self.target {
227 Some(t) => t,
228 None => return Err(anyhow!("SysrootBuilder::target was not called")),
229 };
230 if let Some(manifest) = &self.manifest {
231 if !manifest.exists() {
232 return Err(anyhow!(
233 "Provided manifest did not exist. Path: {}",
234 manifest.display()
235 ));
236 }
237 }
238 // If `target` has an extension, assume target spec...
239 if target.extension().is_some() {
240 // ...and check if it exists.
241 if !target.exists() {
242 return Err(anyhow!(
243 "Provided JSON Target Specification did not exist: {}",
244 target.display()
245 ));
246 }
247 }
248 let rust_src = match &self.rust_src {
249 Some(s) => {
250 if !s.exists() {
251 return Err(anyhow!(
252 "The provided rust-src directory did not exist: {}",
253 s.display()
254 ));
255 }
256 s.clone()
257 }
258 None => {
259 let src = util::get_rust_src().context("Could not detect appropriate rust-src")?;
260 if !src.exists() {
261 return Err(anyhow!("Rust-src component not installed?"));
262 }
263 src
264 }
265 };
266 fs::create_dir_all(&self.output).context("Couldn't create sysroot output directory")?;
267 fs::create_dir_all(artifact_dir(&self.output, target)?)
268 .context("Failed to setup sysroot directory structure")?;
269
270 let sysroot_cargo_toml = generate_sysroot_cargo_toml(&SysrootBuilder {
271 // HACK: So it can see auto-detected rust-src.
272 rust_src: Some(rust_src),
273 ..self.clone()
274 })?;
275 build_alloc(&sysroot_cargo_toml, &self).context("Failed to build sysroot")?;
276
277 // Copy host tools to the new sysroot, so that stuff like proc-macros and
278 // testing can work.
279 util::copy_host_tools(&self.output).context("Couldn't copy host tools to sysroot")?;
280 Ok(self.output.canonicalize().with_context(|| {
281 format!(
282 "Couldn't get canonical path to sysroot: {}",
283 self.output.display()
284 )
285 })?)
286 }
287}
288
289/// Generate a Cargo.toml for building the sysroot crates
290///
291/// Should ONLY be called by [`SysrootBuilder::build`].
292///
293/// See [`build_sysroot_with`].
294fn generate_sysroot_cargo_toml(builder: &SysrootBuilder) -> Result<PathBuf> {
295 fs::write(
296 builder.output.join("lib.rs"),
297 "#![feature(no_core)]\n#![no_core]",
298 )?;
299 let toml = CargoToml {
300 package: Package {
301 name: "Sysroot".into(),
302 version: "0.0.0".into(),
303 authors: vec!["The Rust Project Developers".into(), "DianaNites".into()],
304 edition: Some("2018".into()),
305 autotests: Some(false),
306 autobenches: Some(false),
307 ..Default::default()
308 },
309 lib: Some(TargetConfig {
310 name: Some("sysroot".into()),
311 path: Some("lib.rs".into()),
312 ..Default::default()
313 }),
314 workspace: Some(Workspace::default()),
315 dependencies: Some({
316 let mut deps = BTreeMap::new();
317 match builder.sysroot_crate {
318 Sysroot::Core => {
319 deps.insert(
320 "core".into(),
321 Dependency::Full(DependencyFull {
322 path: Some(builder.rust_src.as_ref().unwrap().join("core")),
323 ..Default::default()
324 }),
325 );
326 }
327
328 Sysroot::CompilerBuiltins => {
329 deps.insert(
330 "compiler_builtins".into(),
331 Dependency::Full(DependencyFull {
332 version: Some("0.1".into()),
333 features: {
334 let mut f = vec!["rustc-dep-of-std".into()];
335 if builder.features.contains(&Features::CompilerBuiltinsMem) {
336 f.push("mem".into());
337 }
338 Some(f)
339 },
340 ..Default::default()
341 }),
342 );
343 }
344
345 Sysroot::Alloc => {
346 deps.insert(
347 "alloc".into(),
348 Dependency::Full(DependencyFull {
349 path: Some(builder.rust_src.as_ref().unwrap().join("alloc")),
350 features: if builder.features.contains(&Features::CompilerBuiltinsMem) {
351 Some(vec!["compiler-builtins-mem".into()])
352 } else {
353 None
354 },
355 ..Default::default()
356 }),
357 );
358 }
359
360 Sysroot::Std => {
361 deps.insert(
362 "std".into(),
363 Dependency::Full(DependencyFull {
364 path: Some(builder.rust_src.as_ref().unwrap().join("std")),
365 features: if builder.features.contains(&Features::CompilerBuiltinsMem) {
366 Some(vec!["compiler-builtins-mem".into()])
367 } else {
368 None
369 },
370 ..Default::default()
371 }),
372 );
373 }
374 }
375 deps
376 }),
377 patch: Some(Patches {
378 sources: if let Sysroot::Core = builder.sysroot_crate {
379 BTreeMap::new()
380 } else {
381 let mut sources = BTreeMap::new();
382 sources.insert("crates-io".into(), {
383 let mut x = BTreeMap::new();
384 x.insert(
385 "rustc-std-workspace-core".to_string(),
386 Dependency::Full(DependencyFull {
387 path: Some(
388 builder
389 .rust_src
390 .as_ref()
391 .unwrap()
392 .join("rustc-std-workspace-core"),
393 ),
394 ..Default::default()
395 }),
396 );
397 x
398 });
399 sources
400 },
401 }),
402 profile: {
403 match &builder.manifest {
404 Some(manifest) => {
405 let toml: CargoToml =
406 from_path(manifest).with_context(|| manifest.display().to_string())?;
407 toml.profile
408 }
409 None => None,
410 }
411 },
412 ..Default::default()
413 };
414 let path = builder.output.join("Cargo.toml");
415 to_path(&path, &toml).context("Failed writing sysroot Cargo.toml")?;
416 Ok(path)
417}
418
419/// The entry-point for building the alloc crate, which builds all the others
420///
421/// Should ONLY be called by [`SysrootBuilder::build`].
422fn build_alloc(alloc_cargo_toml: &Path, builder: &SysrootBuilder) -> Result<()> {
423 let path = alloc_cargo_toml;
424 let triple = builder.target.as_ref().unwrap();
425 let target_dir = builder.output.join("target");
426
427 // TODO: Eat output if up to date? Always? On error?
428 let exit = Command::new(env::var_os("CARGO").unwrap_or_else(|| "cargo".into()))
429 .arg("rustc")
430 .arg("--release")
431 .arg("--target")
432 // If it doesn't work, assume it's a builtin path?
433 .arg(&triple.canonicalize().unwrap_or_else(|_| triple.into()))
434 .arg("--target-dir")
435 .arg(&target_dir)
436 .arg("--manifest-path")
437 .arg(path)
438 .arg("--") // Pass to rustc directly.
439 .arg("-Z")
440 // The rust build system only passes this for rustc? xbuild passes this for alloc. 🤷♀️
441 .arg("force-unstable-if-unmarked")
442 .env("RUSTFLAGS", {
443 let mut env = OsString::new();
444 if let Some(exist) = std::env::var_os("RUSTFLAGS") {
445 env.push(exist);
446 }
447 for flag in &builder.rustc_flags {
448 env.push(" ");
449 env.push(flag)
450 }
451 env
452 })
453 // Causes clippy to leak output
454 // See #6
455 .env_remove("RUSTC_WORKSPACE_WRAPPER")
456 .status()
457 .context("Couldn't find/run cargo command")?;
458 if !exit.success() {
459 return Err(anyhow!(
460 "Failed to build sysroot: Exit code {}",
461 exit.code()
462 .map(|i| i.to_string())
463 .unwrap_or_else(|| "Killed by signal".to_string())
464 ));
465 }
466
467 // Copy artifacts to sysroot.
468 for entry in fs::read_dir(
469 target_dir
470 .join(
471 &triple
472 .file_stem()
473 .context("Failed to parse target triple")?,
474 )
475 .join("release")
476 .join("deps"),
477 )
478 .context("Failure to read artifact directory")?
479 {
480 let entry = entry?;
481 let name = entry
482 .file_name()
483 .into_string()
484 .map_err(|e| Error::msg(e.to_string_lossy().to_string()))
485 .context("Invalid Unicode in path")?;
486 if name.starts_with("lib") {
487 let out = artifact_dir(&builder.output, &triple)?.join(name);
488 fs::copy(entry.path(), &out).with_context(|| {
489 format!(
490 "Copying sysroot artifact from {} to {} failed",
491 entry.path().display(),
492 out.display()
493 )
494 })?;
495 }
496 }
497
498 Ok(())
499}
500
501/// The output artifact directory
502///
503/// Not part of the public API.
504fn artifact_dir(sysroot_dir: &Path, target: &Path) -> Result<PathBuf> {
505 Ok(sysroot_dir
506 .join("lib")
507 .join("rustlib")
508 .join(target.file_stem().context("Invalid Target Specification")?)
509 .join("lib"))
510}
511
512/// Clean up generated sysroot artifacts.
513///
514/// Should be called before [`build_sysroot`] if you want this behavior.
515pub fn clean_artifacts(sysroot_dir: &Path) -> Result<()> {
516 // Clean-up old artifacts
517 match remove_dir_all::remove_dir_all(sysroot_dir) {
518 Ok(_) => (),
519 Err(e) if e.kind() == std::io::ErrorKind::NotFound => (),
520 e => e.context("Couldn't clean sysroot artifacts")?,
521 };
522 Ok(())
523}