mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-29 19:47:38 +00:00
562 lines
21 KiB
Rust
562 lines
21 KiB
Rust
use std::collections::HashMap;
|
|
use std::ffi::OsStr;
|
|
use std::path::{Path, PathBuf};
|
|
use std::{env as std_env, fs};
|
|
|
|
use boml::Toml;
|
|
use boml::types::TomlValue;
|
|
|
|
use crate::utils::{
|
|
create_dir, create_symlink, get_os_name, get_sysroot_dir, run_command_with_output,
|
|
rustc_version_info, split_args,
|
|
};
|
|
|
|
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug)]
|
|
pub enum Channel {
|
|
#[default]
|
|
Debug,
|
|
Release,
|
|
}
|
|
|
|
impl Channel {
|
|
pub fn as_str(self) -> &'static str {
|
|
match self {
|
|
Self::Debug => "debug",
|
|
Self::Release => "release",
|
|
}
|
|
}
|
|
}
|
|
|
|
fn failed_config_parsing(config_file: &Path, err: &str) -> Result<ConfigFile, String> {
|
|
Err(format!("Failed to parse `{}`: {}", config_file.display(), err))
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct ConfigFile {
|
|
gcc_path: Option<String>,
|
|
download_gccjit: Option<bool>,
|
|
}
|
|
|
|
impl ConfigFile {
|
|
pub fn new(config_file: &Path) -> Result<Self, String> {
|
|
let content = fs::read_to_string(config_file).map_err(|_| {
|
|
format!(
|
|
"Failed to read `{}`. Take a look at `Readme.md` to see how to set up the project",
|
|
config_file.display(),
|
|
)
|
|
})?;
|
|
let toml = Toml::parse(&content).map_err(|err| {
|
|
format!("Error occurred around `{}`: {:?}", &content[err.start..=err.end], err.kind)
|
|
})?;
|
|
let mut config = Self::default();
|
|
for (key, value) in toml.iter() {
|
|
match (key, value) {
|
|
("gcc-path", TomlValue::String(value)) => {
|
|
config.gcc_path = Some(value.as_str().to_string())
|
|
}
|
|
("gcc-path", _) => {
|
|
return failed_config_parsing(config_file, "Expected a string for `gcc-path`");
|
|
}
|
|
("download-gccjit", TomlValue::Boolean(value)) => {
|
|
config.download_gccjit = Some(*value)
|
|
}
|
|
("download-gccjit", _) => {
|
|
return failed_config_parsing(
|
|
config_file,
|
|
"Expected a boolean for `download-gccjit`",
|
|
);
|
|
}
|
|
_ => return failed_config_parsing(config_file, &format!("Unknown key `{}`", key)),
|
|
}
|
|
}
|
|
match (config.gcc_path.as_mut(), config.download_gccjit) {
|
|
(None, None | Some(false)) => {
|
|
return failed_config_parsing(
|
|
config_file,
|
|
"At least one of `gcc-path` or `download-gccjit` value must be set",
|
|
);
|
|
}
|
|
(Some(_), Some(true)) => {
|
|
println!(
|
|
"WARNING: both `gcc-path` and `download-gccjit` arguments are used, \
|
|
ignoring `gcc-path`"
|
|
);
|
|
}
|
|
(Some(gcc_path), _) => {
|
|
let path = Path::new(gcc_path);
|
|
*gcc_path = path
|
|
.canonicalize()
|
|
.map_err(|err| {
|
|
format!("Failed to get absolute path of `{}`: {:?}", gcc_path, err)
|
|
})?
|
|
.display()
|
|
.to_string();
|
|
}
|
|
_ => {}
|
|
}
|
|
Ok(config)
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
pub struct ConfigInfo {
|
|
pub target: String,
|
|
pub target_triple: String,
|
|
pub host_triple: String,
|
|
pub rustc_command: Vec<String>,
|
|
pub run_in_vm: bool,
|
|
pub cargo_target_dir: String,
|
|
pub dylib_ext: String,
|
|
pub sysroot_release_channel: bool,
|
|
pub channel: Channel,
|
|
pub sysroot_panic_abort: bool,
|
|
pub cg_backend_path: String,
|
|
pub sysroot_path: String,
|
|
pub gcc_path: Option<String>,
|
|
config_file: Option<String>,
|
|
// This is used in particular in rust compiler bootstrap because it doesn't run at the root
|
|
// of the `cg_gcc` folder, making it complicated for us to get access to local files we need
|
|
// like `libgccjit.version` or `config.toml`.
|
|
cg_gcc_path: Option<PathBuf>,
|
|
// Needed for the `info` command which doesn't want to actually download the lib if needed,
|
|
// just to set the `gcc_path` field to display it.
|
|
pub no_download: bool,
|
|
pub no_default_features: bool,
|
|
pub backend: Option<String>,
|
|
pub features: Vec<String>,
|
|
}
|
|
|
|
impl ConfigInfo {
|
|
/// Returns `true` if the argument was taken into account.
|
|
pub fn parse_argument(
|
|
&mut self,
|
|
arg: &str,
|
|
args: &mut impl Iterator<Item = String>,
|
|
) -> Result<bool, String> {
|
|
match arg {
|
|
"--features" => {
|
|
if let Some(arg) = args.next() {
|
|
self.features.push(arg);
|
|
} else {
|
|
return Err("Expected a value after `--features`, found nothing".to_string());
|
|
}
|
|
}
|
|
"--target" => {
|
|
if let Some(arg) = args.next() {
|
|
self.target = arg;
|
|
} else {
|
|
return Err("Expected a value after `--target`, found nothing".to_string());
|
|
}
|
|
}
|
|
"--target-triple" => match args.next() {
|
|
Some(arg) if !arg.is_empty() => self.target_triple = arg.to_string(),
|
|
_ => {
|
|
return Err(
|
|
"Expected a value after `--target-triple`, found nothing".to_string()
|
|
);
|
|
}
|
|
},
|
|
"--out-dir" => match args.next() {
|
|
Some(arg) if !arg.is_empty() => {
|
|
self.cargo_target_dir = arg.to_string();
|
|
}
|
|
_ => return Err("Expected a value after `--out-dir`, found nothing".to_string()),
|
|
},
|
|
"--config-file" => match args.next() {
|
|
Some(arg) if !arg.is_empty() => {
|
|
self.config_file = Some(arg.to_string());
|
|
}
|
|
_ => {
|
|
return Err("Expected a value after `--config-file`, found nothing".to_string());
|
|
}
|
|
},
|
|
"--release-sysroot" => self.sysroot_release_channel = true,
|
|
"--release" => self.channel = Channel::Release,
|
|
"--sysroot-panic-abort" => self.sysroot_panic_abort = true,
|
|
"--gcc-path" => match args.next() {
|
|
Some(arg) if !arg.is_empty() => {
|
|
self.gcc_path = Some(arg.into());
|
|
}
|
|
_ => {
|
|
return Err("Expected a value after `--gcc-path`, found nothing".to_string());
|
|
}
|
|
},
|
|
"--cg_gcc-path" => match args.next() {
|
|
Some(arg) if !arg.is_empty() => {
|
|
self.cg_gcc_path = Some(arg.into());
|
|
}
|
|
_ => {
|
|
return Err("Expected a value after `--cg_gcc-path`, found nothing".to_string());
|
|
}
|
|
},
|
|
"--use-backend" => match args.next() {
|
|
Some(backend) if !backend.is_empty() => self.backend = Some(backend),
|
|
_ => return Err("Expected an argument after `--use-backend`, found nothing".into()),
|
|
},
|
|
"--no-default-features" => self.no_default_features = true,
|
|
_ => return Ok(false),
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
pub fn rustc_command_vec(&self) -> Vec<&dyn AsRef<OsStr>> {
|
|
let mut command: Vec<&dyn AsRef<OsStr>> = Vec::with_capacity(self.rustc_command.len());
|
|
for arg in self.rustc_command.iter() {
|
|
command.push(arg);
|
|
}
|
|
command
|
|
}
|
|
|
|
pub fn get_gcc_commit(&self) -> Result<String, String> {
|
|
let commit_hash_file = self.compute_path("libgccjit.version");
|
|
let content = fs::read_to_string(&commit_hash_file).map_err(|_| {
|
|
format!(
|
|
"Failed to read `{}`. Take a look at `Readme.md` to see how to set up the project",
|
|
commit_hash_file.display(),
|
|
)
|
|
})?;
|
|
let commit = content.trim();
|
|
// This is a very simple check to ensure this is not a path. For the rest, it'll just fail
|
|
// when trying to download the file so we should be fine.
|
|
if commit.contains('/') || commit.contains('\\') {
|
|
return Err(format!(
|
|
"{}: invalid commit hash `{}`",
|
|
commit_hash_file.display(),
|
|
commit,
|
|
));
|
|
}
|
|
Ok(commit.to_string())
|
|
}
|
|
|
|
fn download_gccjit_if_needed(&mut self) -> Result<(), String> {
|
|
let output_dir = Path::new(crate::BUILD_DIR).join("libgccjit");
|
|
let commit = self.get_gcc_commit()?;
|
|
|
|
let output_dir = output_dir.join(&commit);
|
|
if !output_dir.is_dir() {
|
|
create_dir(&output_dir)?;
|
|
}
|
|
let output_dir = output_dir.canonicalize().map_err(|err| {
|
|
format!("Failed to get absolute path of `{}`: {:?}", output_dir.display(), err)
|
|
})?;
|
|
|
|
let libgccjit_so_name = "libgccjit.so";
|
|
let libgccjit_so = output_dir.join(libgccjit_so_name);
|
|
if !libgccjit_so.is_file() && !self.no_download {
|
|
// Download time!
|
|
let tempfile_name = format!("{}.download", libgccjit_so_name);
|
|
let tempfile = output_dir.join(&tempfile_name);
|
|
let is_in_ci = std::env::var("GITHUB_ACTIONS").is_ok();
|
|
|
|
download_gccjit(&commit, &output_dir, tempfile_name, !is_in_ci)?;
|
|
|
|
let libgccjit_so = output_dir.join(libgccjit_so_name);
|
|
// If we reach this point, it means the file was correctly downloaded, so let's
|
|
// rename it!
|
|
std::fs::rename(&tempfile, &libgccjit_so).map_err(|err| {
|
|
format!(
|
|
"Failed to rename `{}` into `{}`: {:?}",
|
|
tempfile.display(),
|
|
libgccjit_so.display(),
|
|
err,
|
|
)
|
|
})?;
|
|
|
|
println!("Downloaded libgccjit.so version {} successfully!", commit);
|
|
// We need to create a link named `libgccjit.so.0` because that's what the linker is
|
|
// looking for.
|
|
create_symlink(&libgccjit_so, output_dir.join(&format!("{}.0", libgccjit_so_name)))?;
|
|
}
|
|
|
|
let gcc_path = output_dir.display().to_string();
|
|
println!("Using `{}` as path for libgccjit", gcc_path);
|
|
self.gcc_path = Some(gcc_path);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn compute_path<P: AsRef<Path>>(&self, other: P) -> PathBuf {
|
|
match self.cg_gcc_path {
|
|
Some(ref path) => path.join(other),
|
|
None => PathBuf::new().join(other),
|
|
}
|
|
}
|
|
|
|
pub fn setup_gcc_path(&mut self) -> Result<(), String> {
|
|
// If the user used the `--gcc-path` option, no need to look at `config.toml` content
|
|
// since we already have everything we need.
|
|
if let Some(gcc_path) = &self.gcc_path {
|
|
println!(
|
|
"`--gcc-path` was provided, ignoring config file. Using `{}` as path for libgccjit",
|
|
gcc_path
|
|
);
|
|
return Ok(());
|
|
}
|
|
let config_file = match self.config_file.as_deref() {
|
|
Some(config_file) => config_file.into(),
|
|
None => self.compute_path("config.toml"),
|
|
};
|
|
let ConfigFile { gcc_path, download_gccjit } = ConfigFile::new(&config_file)?;
|
|
|
|
if let Some(true) = download_gccjit {
|
|
self.download_gccjit_if_needed()?;
|
|
return Ok(());
|
|
}
|
|
let Some(gcc_path) = gcc_path else {
|
|
return Err(format!("missing `gcc-path` value from `{}`", config_file.display()));
|
|
};
|
|
println!(
|
|
"GCC path retrieved from `{}`. Using `{}` as path for libgccjit",
|
|
config_file.display(),
|
|
gcc_path
|
|
);
|
|
self.gcc_path = Some(gcc_path);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn setup(
|
|
&mut self,
|
|
env: &mut HashMap<String, String>,
|
|
use_system_gcc: bool,
|
|
) -> Result<(), String> {
|
|
env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string());
|
|
|
|
let gcc_path = if !use_system_gcc {
|
|
if self.gcc_path.is_none() {
|
|
self.setup_gcc_path()?;
|
|
}
|
|
self.gcc_path.clone().expect(
|
|
"The config module should have emitted an error if the GCC path wasn't provided",
|
|
)
|
|
} else {
|
|
String::new()
|
|
};
|
|
env.insert("GCC_PATH".to_string(), gcc_path.clone());
|
|
|
|
if self.cargo_target_dir.is_empty() {
|
|
match env.get("CARGO_TARGET_DIR").filter(|dir| !dir.is_empty()) {
|
|
Some(cargo_target_dir) => self.cargo_target_dir = cargo_target_dir.clone(),
|
|
None => self.cargo_target_dir = "target/out".to_string(),
|
|
}
|
|
}
|
|
|
|
let os_name = get_os_name()?;
|
|
self.dylib_ext = match os_name.as_str() {
|
|
"Linux" => "so",
|
|
"Darwin" => "dylib",
|
|
os => return Err(format!("unsupported OS `{}`", os)),
|
|
}
|
|
.to_string();
|
|
let rustc = match env.get("RUSTC") {
|
|
Some(r) if !r.is_empty() => r.to_string(),
|
|
_ => "rustc".to_string(),
|
|
};
|
|
self.host_triple = match rustc_version_info(Some(&rustc))?.host {
|
|
Some(host) => host,
|
|
None => return Err("no host found".to_string()),
|
|
};
|
|
|
|
if self.target_triple.is_empty() {
|
|
if let Some(overwrite) = env.get("OVERWRITE_TARGET_TRIPLE") {
|
|
self.target_triple = overwrite.clone();
|
|
}
|
|
}
|
|
if self.target_triple.is_empty() {
|
|
self.target_triple = self.host_triple.clone();
|
|
}
|
|
if self.target.is_empty() && !self.target_triple.is_empty() {
|
|
self.target = self.target_triple.clone();
|
|
}
|
|
|
|
let mut linker = None;
|
|
|
|
if self.host_triple != self.target_triple {
|
|
if self.target_triple.is_empty() {
|
|
return Err("Unknown non-native platform".to_string());
|
|
}
|
|
linker = Some(format!("-Clinker={}-gcc", self.target_triple));
|
|
self.run_in_vm = true;
|
|
}
|
|
|
|
let current_dir =
|
|
std_env::current_dir().map_err(|error| format!("`current_dir` failed: {:?}", error))?;
|
|
let channel = if self.channel == Channel::Release {
|
|
"release"
|
|
} else if let Some(channel) = env.get("CHANNEL") {
|
|
channel.as_str()
|
|
} else {
|
|
"debug"
|
|
};
|
|
|
|
let mut rustflags = Vec::new();
|
|
self.cg_backend_path = current_dir
|
|
.join("target")
|
|
.join(channel)
|
|
.join(&format!("librustc_codegen_gcc.{}", self.dylib_ext))
|
|
.display()
|
|
.to_string();
|
|
self.sysroot_path =
|
|
current_dir.join(&get_sysroot_dir()).join("sysroot").display().to_string();
|
|
if let Some(backend) = &self.backend {
|
|
// This option is only used in the rust compiler testsuite. The sysroot is handled
|
|
// by its build system directly so no need to set it ourselves.
|
|
rustflags.push(format!("-Zcodegen-backend={}", backend));
|
|
} else {
|
|
rustflags.extend_from_slice(&[
|
|
"--sysroot".to_string(),
|
|
self.sysroot_path.clone(),
|
|
format!("-Zcodegen-backend={}", self.cg_backend_path),
|
|
]);
|
|
}
|
|
|
|
// This environment variable is useful in case we want to change options of rustc commands.
|
|
// We have a different environment variable than RUSTFLAGS to make sure those flags are
|
|
// only sent to rustc_codegen_gcc and not the LLVM backend.
|
|
if let Some(cg_rustflags) = env.get("CG_RUSTFLAGS") {
|
|
rustflags.extend_from_slice(&split_args(&cg_rustflags)?);
|
|
}
|
|
if let Some(test_flags) = env.get("TEST_FLAGS") {
|
|
rustflags.extend_from_slice(&split_args(&test_flags)?);
|
|
}
|
|
|
|
if let Some(linker) = linker {
|
|
rustflags.push(linker.to_string());
|
|
}
|
|
|
|
if self.no_default_features {
|
|
rustflags.push("-Csymbol-mangling-version=v0".to_string());
|
|
}
|
|
|
|
// FIXME(antoyo): remove once the atomic shim is gone
|
|
if os_name == "Darwin" {
|
|
rustflags.extend_from_slice(&[
|
|
"-Clink-arg=-undefined".to_string(),
|
|
"-Clink-arg=dynamic_lookup".to_string(),
|
|
]);
|
|
}
|
|
env.insert("RUSTFLAGS".to_string(), rustflags.join(" "));
|
|
// display metadata load errors
|
|
env.insert("RUSTC_LOG".to_string(), "warn".to_string());
|
|
|
|
let sysroot = current_dir
|
|
.join(&get_sysroot_dir())
|
|
.join(&format!("sysroot/lib/rustlib/{}/lib", self.target_triple));
|
|
let ld_library_path = format!(
|
|
"{target}:{sysroot}:{gcc_path}",
|
|
target = self.cargo_target_dir,
|
|
sysroot = sysroot.display(),
|
|
gcc_path = gcc_path,
|
|
);
|
|
env.insert("LIBRARY_PATH".to_string(), ld_library_path.clone());
|
|
env.insert("LD_LIBRARY_PATH".to_string(), ld_library_path.clone());
|
|
env.insert("DYLD_LIBRARY_PATH".to_string(), ld_library_path);
|
|
|
|
// NOTE: To avoid the -fno-inline errors, use /opt/gcc/bin/gcc instead of cc.
|
|
// To do so, add a symlink for cc to /opt/gcc/bin/gcc in our PATH.
|
|
// Another option would be to add the following Rust flag: -Clinker=/opt/gcc/bin/gcc
|
|
let path = std::env::var("PATH").unwrap_or_default();
|
|
env.insert(
|
|
"PATH".to_string(),
|
|
format!(
|
|
"/opt/gcc/bin:/opt/m68k-unknown-linux-gnu/bin{}{}",
|
|
if path.is_empty() { "" } else { ":" },
|
|
path
|
|
),
|
|
);
|
|
|
|
self.rustc_command = vec![rustc];
|
|
self.rustc_command.extend_from_slice(&rustflags);
|
|
self.rustc_command.extend_from_slice(&[
|
|
"-L".to_string(),
|
|
format!("crate={}", self.cargo_target_dir),
|
|
"--out-dir".to_string(),
|
|
self.cargo_target_dir.clone(),
|
|
]);
|
|
|
|
if !env.contains_key("RUSTC_LOG") {
|
|
env.insert("RUSTC_LOG".to_string(), "warn".to_string());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn show_usage() {
|
|
println!(
|
|
"\
|
|
--features [arg] : Add a new feature [arg]
|
|
--target-triple [arg] : Set the target triple to [arg]
|
|
--target [arg] : Set the target to [arg]
|
|
--out-dir : Location where the files will be generated
|
|
--release : Build in release mode
|
|
--release-sysroot : Build sysroot in release mode
|
|
--sysroot-panic-abort : Build the sysroot without unwinding support
|
|
--config-file : Location of the config file to be used
|
|
--gcc-path : Location of the GCC root folder
|
|
--cg_gcc-path : Location of the rustc_codegen_gcc root folder (used
|
|
when ran from another directory)
|
|
--no-default-features : Add `--no-default-features` flag to cargo commands
|
|
--use-backend : Useful only for rustc testsuite"
|
|
);
|
|
}
|
|
}
|
|
|
|
fn download_gccjit(
|
|
commit: &str,
|
|
output_dir: &Path,
|
|
tempfile_name: String,
|
|
with_progress_bar: bool,
|
|
) -> Result<(), String> {
|
|
let url = if std::env::consts::OS == "linux" && std::env::consts::ARCH == "x86_64" {
|
|
format!("https://github.com/rust-lang/gcc/releases/download/master-{}/libgccjit.so", commit)
|
|
} else {
|
|
eprintln!(
|
|
"\
|
|
Pre-compiled libgccjit.so not available for this os or architecture.
|
|
Please compile it yourself and update the `config.toml` file
|
|
to `download-gccjit = false` and set `gcc-path` to the appropriate directory."
|
|
);
|
|
return Err(String::from(
|
|
"no appropriate pre-compiled libgccjit.so available for download",
|
|
));
|
|
};
|
|
|
|
println!("Downloading `{}`...", url);
|
|
|
|
// Try curl. If that fails and we are on windows, fallback to PowerShell.
|
|
let mut ret = run_command_with_output(
|
|
&[
|
|
&"curl",
|
|
&"--speed-time",
|
|
&"30",
|
|
&"--speed-limit",
|
|
&"10", // timeout if speed is < 10 bytes/sec for > 30 seconds
|
|
&"--connect-timeout",
|
|
&"30", // timeout if cannot connect within 30 seconds
|
|
&"-o",
|
|
&tempfile_name,
|
|
&"--retry",
|
|
&"3",
|
|
&"-SRfL",
|
|
if with_progress_bar { &"--progress-bar" } else { &"-s" },
|
|
&url.as_str(),
|
|
],
|
|
Some(&output_dir),
|
|
);
|
|
if ret.is_err() && cfg!(windows) {
|
|
eprintln!("Fallback to PowerShell");
|
|
ret = run_command_with_output(
|
|
&[
|
|
&"PowerShell.exe",
|
|
&"/nologo",
|
|
&"-Command",
|
|
&"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
|
|
&format!(
|
|
"(New-Object System.Net.WebClient).DownloadFile('{}', '{}')",
|
|
url, tempfile_name,
|
|
)
|
|
.as_str(),
|
|
],
|
|
Some(&output_dir),
|
|
);
|
|
}
|
|
ret
|
|
}
|