rust/build_system/src/config.rs

533 lines
19 KiB
Rust
Raw Normal View History

use crate::utils::{
create_symlink, get_os_name, run_command_with_output, rustc_version_info, split_args,
};
2023-09-26 14:09:51 +00:00
use std::collections::HashMap;
use std::env as std_env;
2023-12-01 22:57:16 +00:00
use std::ffi::OsStr;
use std::fs;
2024-02-12 17:07:05 +00:00
use std::path::{Path, PathBuf};
use boml::{types::TomlValue, Toml};
2023-09-26 14:09:51 +00:00
2023-12-13 20:35:05 +00:00
#[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",
}
}
}
2024-02-12 17:07:05 +00:00
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 {
2024-02-12 17:07:05 +00:00
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",
2024-02-12 17:07:05 +00:00
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)),
}
}
2024-02-12 17:07:05 +00:00
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)
}
}
2023-12-01 22:57:16 +00:00
#[derive(Default, Debug)]
2023-09-26 14:09:51 +00:00
pub struct ConfigInfo {
2023-12-16 16:55:53 +00:00
pub target: String,
2023-09-26 14:09:51 +00:00
pub target_triple: String,
2023-11-08 16:31:56 +00:00
pub host_triple: String,
2023-09-26 14:09:51 +00:00
pub rustc_command: Vec<String>,
2023-11-08 16:31:56 +00:00
pub run_in_vm: bool,
pub cargo_target_dir: String,
pub dylib_ext: String,
pub sysroot_release_channel: bool,
2023-12-13 20:35:05 +00:00
pub channel: Channel,
2023-11-08 16:31:56 +00:00
pub sysroot_panic_abort: bool,
2023-12-05 20:09:13 +00:00
pub cg_backend_path: String,
pub sysroot_path: String,
pub gcc_path: String,
config_file: Option<String>,
2024-02-12 17:07:05 +00:00
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,
2023-09-26 14:09:51 +00:00
}
2023-11-08 16:31:56 +00:00
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 {
2023-12-16 16:55:53 +00:00
"--target" => {
if let Some(arg) = args.next() {
self.target = arg;
} else {
return Err("Expected a value after `--target`, found nothing".to_string());
}
}
2023-11-08 16:31:56 +00:00
"--target-triple" => match args.next() {
Some(arg) if !arg.is_empty() => self.target_triple = arg.to_string(),
_ => {
2023-09-06 23:01:04 +00:00
return Err(
"Expected a value after `--target-triple`, found nothing".to_string()
2023-11-08 16:31:56 +00:00
)
2023-09-06 23:01:04 +00:00
}
},
2023-11-08 16:31:56 +00:00
"--out-dir" => match args.next() {
Some(arg) if !arg.is_empty() => {
self.cargo_target_dir = arg.to_string();
}
2023-11-08 16:31:56 +00:00
_ => 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())
}
},
2023-11-08 16:31:56 +00:00
"--release-sysroot" => self.sysroot_release_channel = true,
2023-12-13 20:35:05 +00:00
"--release" => self.channel = Channel::Release,
2023-11-08 16:31:56 +00:00
"--sysroot-panic-abort" => self.sysroot_panic_abort = true,
2024-02-12 17:07:05 +00:00
"--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())
}
},
2023-11-08 16:31:56 +00:00
_ => return Ok(false),
2023-09-26 14:09:51 +00:00
}
2023-11-08 16:31:56 +00:00
Ok(true)
2023-09-26 14:09:51 +00:00
}
2023-09-06 23:01:04 +00:00
2023-12-01 22:57:16 +00:00
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
}
2024-02-12 17:07:05 +00:00
fn download_gccjit_if_needed(&mut self) -> Result<(), String> {
let output_dir = Path::new(
std::env::var("CARGO_TARGET_DIR")
.as_deref()
.unwrap_or("target"),
)
.join("libgccjit");
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();
if commit.contains('/') || commit.contains('\\') {
return Err(format!(
"{}: invalid commit hash `{}`",
commit_hash_file.display(),
commit,
2024-02-12 17:07:05 +00:00
));
}
let output_dir = output_dir.join(commit);
if !output_dir.is_dir() {
std::fs::create_dir_all(&output_dir).map_err(|err| {
format!(
"failed to create folder `{}`: {:?}",
output_dir.display(),
err,
)
})?;
}
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 {
2024-02-12 17:07:05 +00:00
// Download time!
let tempfile_name = "libgccjit.so.download";
let tempfile = output_dir.join(tempfile_name);
let is_in_ci = std::env::var("GITHUB_ACTIONS").is_ok();
let url = format!(
"https://github.com/antoyo/gcc/releases/download/master-{}/libgccjit.so",
commit,
);
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 is_in_ci { &"-s" } else { &"--progress-bar" },
&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?;
// 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);
create_symlink(
&libgccjit_so,
output_dir.join(&format!("{}.0", libgccjit_so_name)),
)?;
2024-02-12 17:07:05 +00:00
}
self.gcc_path = output_dir.display().to_string();
2024-02-12 17:07:05 +00:00
println!("Using `{}` as path for libgccjit", self.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),
}
}
2024-02-12 13:41:18 +00:00
pub fn setup_gcc_path(&mut self) -> Result<(), String> {
let config_file = match self.config_file.as_deref() {
Some(config_file) => config_file.into(),
None => self.compute_path("config.toml"),
};
2024-02-12 17:07:05 +00:00
let ConfigFile {
gcc_path,
download_gccjit,
} = ConfigFile::new(&config_file)?;
2024-02-12 17:07:05 +00:00
if let Some(true) = download_gccjit {
self.download_gccjit_if_needed()?;
return Ok(());
}
2024-02-12 13:41:18 +00:00
self.gcc_path = match gcc_path {
Some(path) => path,
None => {
2024-02-12 13:41:18 +00:00
return Err(format!(
"missing `gcc-path` value from `{}`",
2024-02-12 17:07:05 +00:00
config_file.display(),
2024-02-12 13:41:18 +00:00
))
}
};
Ok(())
}
2023-11-08 16:31:56 +00:00
pub fn setup(
&mut self,
env: &mut HashMap<String, String>,
2024-02-12 13:41:18 +00:00
use_system_gcc: bool,
2023-11-08 16:31:56 +00:00
) -> Result<(), String> {
env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string());
2024-02-12 13:41:18 +00:00
if self.gcc_path.is_empty() && !use_system_gcc {
self.setup_gcc_path()?;
}
env.insert("GCC_PATH".to_string(), self.gcc_path.clone());
2023-11-08 16:31:56 +00:00
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()),
};
2023-11-08 16:31:56 +00:00
if self.target_triple.is_empty() {
if let Some(overwrite) = env.get("OVERWRITE_TARGET_TRIPLE") {
self.target_triple = overwrite.clone();
}
}
2023-11-08 16:31:56 +00:00
if self.target_triple.is_empty() {
self.target_triple = self.host_triple.clone();
}
2023-12-16 16:55:53 +00:00
if self.target.is_empty() && !self.target_triple.is_empty() {
self.target = self.target_triple.clone();
}
2023-11-08 16:31:56 +00:00
let mut linker = None;
if self.host_triple != self.target_triple {
2023-12-01 22:57:16 +00:00
if self.target_triple.is_empty() {
2023-11-08 16:31:56 +00:00
return Err("Unknown non-native platform".to_string());
}
2023-12-01 22:57:16 +00:00
linker = Some(format!("-Clinker={}-gcc", self.target_triple));
2023-11-08 16:31:56 +00:00
self.run_in_vm = true;
}
let current_dir =
std_env::current_dir().map_err(|error| format!("`current_dir` failed: {:?}", error))?;
2023-12-13 20:35:05 +00:00
let channel = if self.channel == Channel::Release {
2023-11-22 14:17:48 +00:00
"release"
} else if let Some(channel) = env.get("CHANNEL") {
2023-11-08 16:31:56 +00:00
channel.as_str()
} else {
"debug"
};
let has_builtin_backend = env
.get("BUILTIN_BACKEND")
.map(|backend| !backend.is_empty())
.unwrap_or(false);
let mut rustflags = Vec::new();
if has_builtin_backend {
// It means we're building inside the rustc testsuite, so some options need to be handled
// a bit differently.
2023-12-05 20:09:13 +00:00
self.cg_backend_path = "gcc".to_string();
2023-11-08 16:31:56 +00:00
match env.get("RUSTC_SYSROOT") {
Some(rustc_sysroot) if !rustc_sysroot.is_empty() => {
rustflags.extend_from_slice(&["--sysroot".to_string(), rustc_sysroot.clone()]);
}
_ => {}
}
// This should not be needed, but is necessary for the CI in the rust repository.
// FIXME: Remove when the rust CI switches to the master version of libgccjit.
2023-11-08 16:31:56 +00:00
rustflags.push("-Cpanic=abort".to_string());
} else {
2023-12-05 20:09:13 +00:00
self.cg_backend_path = current_dir
2023-11-08 16:31:56 +00:00
.join("target")
.join(channel)
.join(&format!("librustc_codegen_gcc.{}", self.dylib_ext))
.display()
.to_string();
2023-12-05 20:09:13 +00:00
self.sysroot_path = current_dir
.join("build_sysroot/sysroot")
.display()
.to_string();
rustflags.extend_from_slice(&["--sysroot".to_string(), self.sysroot_path.clone()]);
2023-11-08 16:31:56 +00:00
};
// This environment variable is useful in case we want to change options of rustc commands.
2023-11-08 16:31:56 +00:00
if let Some(cg_rustflags) = env.get("CG_RUSTFLAGS") {
2023-12-01 22:57:16 +00:00
rustflags.extend_from_slice(&split_args(&cg_rustflags)?);
2023-11-08 16:31:56 +00:00
}
2023-12-13 20:35:05 +00:00
if let Some(test_flags) = env.get("TEST_FLAGS") {
rustflags.extend_from_slice(&split_args(&test_flags)?);
}
2023-11-08 16:31:56 +00:00
if let Some(linker) = linker {
rustflags.push(linker.to_string());
}
2023-09-26 14:09:51 +00:00
rustflags.extend_from_slice(&[
2023-11-08 16:31:56 +00:00
"-Csymbol-mangling-version=v0".to_string(),
"-Cdebuginfo=2".to_string(),
2023-12-05 20:09:13 +00:00
format!("-Zcodegen-backend={}", self.cg_backend_path),
2023-09-26 14:09:51 +00:00
]);
2023-11-08 16:31:56 +00:00
// Since we don't support ThinLTO, disable LTO completely when not trying to do LTO.
// TODO(antoyo): remove when we can handle ThinLTO.
if !env.contains_key(&"FAT_LTO".to_string()) {
rustflags.push("-Clto=off".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(&format!(
"build_sysroot/sysroot/lib/rustlib/{}/lib",
self.target_triple,
));
let ld_library_path = format!(
"{target}:{sysroot}:{gcc_path}",
// FIXME: It's possible to pick another out directory. Would be nice to have a command
// line option to change it.
2023-11-08 16:31:56 +00:00
target = current_dir.join("target/out").display(),
sysroot = sysroot.display(),
gcc_path = self.gcc_path,
2023-11-08 16:31:56 +00:00
);
env.insert("LIBRARY_PATH".to_string(), ld_library_path.clone());
2023-11-08 16:31:56 +00:00
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(),
"crate=target/out".to_string(),
"--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!(
"\
--target-triple [arg] : Set the target triple to [arg]
2023-12-16 16:55:53 +00:00
--target [arg] : Set the target to [arg]
2023-11-08 16:31:56 +00:00
--out-dir : Location where the files will be generated
2023-12-13 20:35:05 +00:00
--release : Build in release mode
2023-11-08 16:31:56 +00:00
--release-sysroot : Build sysroot in release mode
--sysroot-panic-abort : Build the sysroot without unwinding support
2024-02-12 17:07:05 +00:00
--config-file : Location of the config file to be used
--cg_gcc-path : Location of the rustc_codegen_gcc root folder (used
for accessing any file from the project)"
2023-11-08 16:31:56 +00:00
);
2023-09-26 14:09:51 +00:00
}
}