use anyhow::*;
use cargo_toml2::{
from_path,
to_path,
CargoToml,
Dependency,
DependencyFull,
Package,
Patches,
TargetConfig,
Workspace,
};
use std::{
collections::BTreeMap,
env,
ffi::OsString,
fs,
path::{Path, PathBuf},
process::Command,
};
mod util;
pub use util::get_rust_src;
#[derive(Debug, Copy, Clone)]
pub enum Sysroot {
Core,
CompilerBuiltins,
Alloc,
Std,
}
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
pub enum Features {
CompilerBuiltinsMem,
CompilerBuiltinsC,
CompilerBuiltinsNoAsm,
}
#[derive(Debug, Clone)]
pub struct SysrootBuilder {
manifest: Option<PathBuf>,
output: PathBuf,
target: Option<PathBuf>,
rust_src: Option<PathBuf>,
sysroot_crate: Sysroot,
features: Vec<Features>,
rustc_flags: Vec<OsString>,
}
impl SysrootBuilder {
pub fn new(sysroot_crate: Sysroot) -> Self {
Self {
manifest: Default::default(),
output: PathBuf::from(".").join("target").join("sysroot"),
target: Default::default(),
rust_src: Default::default(),
sysroot_crate,
features: Vec::with_capacity(3),
rustc_flags: Default::default(),
}
}
pub fn manifest(&mut self, manifest: PathBuf) -> &mut Self {
self.manifest = Some(manifest);
self
}
pub fn output(&mut self, output: PathBuf) -> &mut Self {
self.output = output;
self
}
pub fn target(&mut self, target: PathBuf) -> &mut Self {
self.target = Some(target);
self
}
pub fn rust_src(&mut self, rust_src: PathBuf) -> &mut Self {
self.rust_src = Some(rust_src);
self
}
pub fn features(&mut self, features: &[Features]) -> &mut Self {
self.features.extend_from_slice(features);
self.features.sort_unstable();
self.features.dedup();
self
}
pub fn rustc_flags<I, S>(&mut self, flags: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
self.rustc_flags.extend(flags.into_iter().map(Into::into));
self
}
pub fn build(&self) -> Result<PathBuf> {
let target = match &self.target {
Some(t) => t,
None => return Err(anyhow!("SysrootBuilder::target was not called")),
};
if let Some(manifest) = &self.manifest {
if !manifest.exists() {
return Err(anyhow!(
"Provided manifest did not exist. Path: {}",
manifest.display()
));
}
}
if target.extension().is_some() {
if !target.exists() {
return Err(anyhow!(
"Provided JSON Target Specification did not exist: {}",
target.display()
));
}
}
let rust_src = match &self.rust_src {
Some(s) => {
if !s.exists() {
return Err(anyhow!(
"The provided rust-src directory did not exist: {}",
s.display()
));
}
s.clone()
}
None => {
let src = util::get_rust_src().context("Could not detect appropriate rust-src")?;
if !src.exists() {
return Err(anyhow!("Rust-src component not installed?"));
}
src
}
};
fs::create_dir_all(&self.output).context("Couldn't create sysroot output directory")?;
fs::create_dir_all(artifact_dir(&self.output, target)?)
.context("Failed to setup sysroot directory structure")?;
let sysroot_cargo_toml = generate_sysroot_cargo_toml(&SysrootBuilder {
rust_src: Some(rust_src),
..self.clone()
})?;
build_alloc(&sysroot_cargo_toml, &self).context("Failed to build sysroot")?;
util::copy_host_tools(&self.output).context("Couldn't copy host tools to sysroot")?;
Ok(self.output.canonicalize().with_context(|| {
format!(
"Couldn't get canonical path to sysroot: {}",
self.output.display()
)
})?)
}
}
fn generate_sysroot_cargo_toml(builder: &SysrootBuilder) -> Result<PathBuf> {
fs::write(
builder.output.join("lib.rs"),
"#![feature(no_core)]\n#![no_core]",
)?;
let toml = CargoToml {
package: Package {
name: "Sysroot".into(),
version: "0.0.0".into(),
authors: vec!["The Rust Project Developers".into(), "DianaNites".into()],
edition: Some("2018".into()),
autotests: Some(false),
autobenches: Some(false),
..Default::default()
},
lib: Some(TargetConfig {
name: Some("sysroot".into()),
path: Some("lib.rs".into()),
..Default::default()
}),
workspace: Some(Workspace::default()),
dependencies: Some({
let mut deps = BTreeMap::new();
match builder.sysroot_crate {
Sysroot::Core => {
deps.insert(
"core".into(),
Dependency::Full(DependencyFull {
path: Some(builder.rust_src.as_ref().unwrap().join("core")),
..Default::default()
}),
);
}
Sysroot::CompilerBuiltins => {
deps.insert(
"compiler_builtins".into(),
Dependency::Full(DependencyFull {
version: Some("0.1".into()),
features: {
let mut f = vec!["rustc-dep-of-std".into()];
if builder.features.contains(&Features::CompilerBuiltinsMem) {
f.push("mem".into());
}
Some(f)
},
..Default::default()
}),
);
}
Sysroot::Alloc => {
deps.insert(
"alloc".into(),
Dependency::Full(DependencyFull {
path: Some(builder.rust_src.as_ref().unwrap().join("alloc")),
features: if builder.features.contains(&Features::CompilerBuiltinsMem) {
Some(vec!["compiler-builtins-mem".into()])
} else {
None
},
..Default::default()
}),
);
}
Sysroot::Std => {
deps.insert(
"std".into(),
Dependency::Full(DependencyFull {
path: Some(builder.rust_src.as_ref().unwrap().join("std")),
features: if builder.features.contains(&Features::CompilerBuiltinsMem) {
Some(vec!["compiler-builtins-mem".into()])
} else {
None
},
..Default::default()
}),
);
}
}
deps
}),
patch: Some(Patches {
sources: if let Sysroot::Core = builder.sysroot_crate {
BTreeMap::new()
} else {
let mut sources = BTreeMap::new();
sources.insert("crates-io".into(), {
let mut x = BTreeMap::new();
x.insert(
"rustc-std-workspace-core".to_string(),
Dependency::Full(DependencyFull {
path: Some(
builder
.rust_src
.as_ref()
.unwrap()
.join("rustc-std-workspace-core"),
),
..Default::default()
}),
);
x
});
sources
},
}),
profile: {
match &builder.manifest {
Some(manifest) => {
let toml: CargoToml =
from_path(manifest).with_context(|| manifest.display().to_string())?;
toml.profile
}
None => None,
}
},
..Default::default()
};
let path = builder.output.join("Cargo.toml");
to_path(&path, &toml).context("Failed writing sysroot Cargo.toml")?;
Ok(path)
}
fn build_alloc(alloc_cargo_toml: &Path, builder: &SysrootBuilder) -> Result<()> {
let path = alloc_cargo_toml;
let triple = builder.target.as_ref().unwrap();
let target_dir = builder.output.join("target");
let exit = Command::new(env::var_os("CARGO").unwrap_or_else(|| "cargo".into()))
.arg("rustc")
.arg("--release")
.arg("--target")
.arg(&triple.canonicalize().unwrap_or_else(|_| triple.into()))
.arg("--target-dir")
.arg(&target_dir)
.arg("--manifest-path")
.arg(path)
.arg("--")
.arg("-Z")
.arg("force-unstable-if-unmarked")
.env("RUSTFLAGS", {
let mut env = OsString::new();
if let Some(exist) = std::env::var_os("RUSTFLAGS") {
env.push(exist);
}
for flag in &builder.rustc_flags {
env.push(" ");
env.push(flag)
}
env
})
.status()
.context("Couldn't find/run cargo command")?;
if !exit.success() {
return Err(anyhow!(
"Failed to build sysroot: Exit code {}",
exit.code()
.map(|i| i.to_string())
.unwrap_or_else(|| "Killed by signal".to_string())
));
}
for entry in fs::read_dir(
target_dir
.join(
&triple
.file_stem()
.context("Failed to parse target triple")?,
)
.join("release")
.join("deps"),
)
.context("Failure to read artifact directory")?
{
let entry = entry?;
let name = entry
.file_name()
.into_string()
.map_err(|e| Error::msg(e.to_string_lossy().to_string()))
.context("Invalid Unicode in path")?;
if name.starts_with("lib") {
let out = artifact_dir(&builder.output, &triple)?.join(name);
fs::copy(entry.path(), &out).with_context(|| {
format!(
"Copying sysroot artifact from {} to {} failed",
entry.path().display(),
out.display()
)
})?;
}
}
Ok(())
}
fn artifact_dir(sysroot_dir: &Path, target: &Path) -> Result<PathBuf> {
Ok(sysroot_dir
.join("lib")
.join("rustlib")
.join(target.file_stem().context("Invalid Target Specification")?)
.join("lib"))
}
pub fn clean_artifacts(sysroot_dir: &Path) -> Result<()> {
match remove_dir_all::remove_dir_all(sysroot_dir) {
Ok(_) => (),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => (),
e => e.context("Couldn't clean sysroot artifacts")?,
};
Ok(())
}