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}