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