Skip to main content

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}