Merge commit 'b385428e3d
' into subtree-update_cg_gcc_2024-03-05
This commit is contained in:
commit
0d359efbe6
76 changed files with 7183 additions and 4278 deletions
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "boml"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85fdb93f04c73bff54305fa437ffea5449c41edcaadfe882f35836206b166ac5"
|
||||
|
||||
[[package]]
|
||||
name = "y"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"boml",
|
||||
]
|
||||
|
|
|
@ -3,6 +3,9 @@ name = "y"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
boml = "0.3.1"
|
||||
|
||||
[[bin]]
|
||||
name = "y"
|
||||
path = "src/main.rs"
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use crate::config::{set_config, ConfigInfo};
|
||||
use crate::utils::{
|
||||
get_gcc_path, run_command, run_command_with_output_and_env, walk_dir,
|
||||
};
|
||||
use crate::config::{Channel, ConfigInfo};
|
||||
use crate::utils::{run_command, run_command_with_output_and_env, walk_dir};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
|
@ -9,33 +7,18 @@ use std::path::Path;
|
|||
|
||||
#[derive(Default)]
|
||||
struct BuildArg {
|
||||
codegen_release_channel: bool,
|
||||
sysroot_release_channel: bool,
|
||||
sysroot_panic_abort: bool,
|
||||
flags: Vec<String>,
|
||||
gcc_path: String,
|
||||
config_info: ConfigInfo,
|
||||
}
|
||||
|
||||
impl BuildArg {
|
||||
fn new() -> Result<Option<Self>, String> {
|
||||
let gcc_path = get_gcc_path()?;
|
||||
let mut build_arg = Self {
|
||||
gcc_path,
|
||||
..Default::default()
|
||||
};
|
||||
let mut build_arg = Self::default();
|
||||
// We skip binary name and the `build` command.
|
||||
let mut args = std::env::args().skip(2);
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--release" => build_arg.codegen_release_channel = true,
|
||||
"--release-sysroot" => build_arg.sysroot_release_channel = true,
|
||||
"--no-default-features" => {
|
||||
build_arg.flags.push("--no-default-features".to_string());
|
||||
}
|
||||
"--sysroot-panic-abort" => {
|
||||
build_arg.sysroot_panic_abort = true;
|
||||
},
|
||||
"--features" => {
|
||||
if let Some(arg) = args.next() {
|
||||
build_arg.flags.push("--features".to_string());
|
||||
|
@ -50,25 +33,11 @@ impl BuildArg {
|
|||
Self::usage();
|
||||
return Ok(None);
|
||||
}
|
||||
"--target-triple" => {
|
||||
if args.next().is_some() {
|
||||
// Handled in config.rs.
|
||||
} else {
|
||||
return Err(
|
||||
"Expected a value after `--target-triple`, found nothing".to_string()
|
||||
);
|
||||
arg => {
|
||||
if !build_arg.config_info.parse_argument(arg, &mut args)? {
|
||||
return Err(format!("Unknown argument `{}`", arg));
|
||||
}
|
||||
}
|
||||
"--target" => {
|
||||
if args.next().is_some() {
|
||||
// Handled in config.rs.
|
||||
} else {
|
||||
return Err(
|
||||
"Expected a value after `--target`, found nothing".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
arg => return Err(format!("Unknown argument `{}`", arg)),
|
||||
}
|
||||
}
|
||||
Ok(Some(build_arg))
|
||||
|
@ -79,29 +48,19 @@ impl BuildArg {
|
|||
r#"
|
||||
`build` command help:
|
||||
|
||||
--release : Build codegen in release mode
|
||||
--release-sysroot : Build sysroot in release mode
|
||||
--sysroot-panic-abort : Build the sysroot without unwinding support.
|
||||
--no-default-features : Add `--no-default-features` flag
|
||||
--features [arg] : Add a new feature [arg]
|
||||
--target-triple [arg] : Set the target triple to [arg]
|
||||
--help : Show this help
|
||||
"#
|
||||
)
|
||||
--features [arg] : Add a new feature [arg]"#
|
||||
);
|
||||
ConfigInfo::show_usage();
|
||||
println!(" --help : Show this help");
|
||||
}
|
||||
}
|
||||
|
||||
fn build_sysroot(
|
||||
env: &mut HashMap<String, String>,
|
||||
args: &BuildArg,
|
||||
config: &ConfigInfo,
|
||||
) -> Result<(), String> {
|
||||
std::env::set_current_dir("build_sysroot")
|
||||
.map_err(|error| format!("Failed to go to `build_sysroot` directory: {:?}", error))?;
|
||||
pub fn build_sysroot(env: &HashMap<String, String>, config: &ConfigInfo) -> Result<(), String> {
|
||||
let start_dir = Path::new("build_sysroot");
|
||||
// Cleanup for previous run
|
||||
// Clean target dir except for build scripts and incremental cache
|
||||
let _ = walk_dir(
|
||||
"target",
|
||||
start_dir.join("target"),
|
||||
|dir: &Path| {
|
||||
for top in &["debug", "release"] {
|
||||
let _ = fs::remove_dir_all(dir.join(top).join("build"));
|
||||
|
@ -138,92 +97,114 @@ fn build_sysroot(
|
|||
|_| Ok(()),
|
||||
);
|
||||
|
||||
let _ = fs::remove_file("Cargo.lock");
|
||||
let _ = fs::remove_file("test_target/Cargo.lock");
|
||||
let _ = fs::remove_dir_all("sysroot");
|
||||
let _ = fs::remove_file(start_dir.join("Cargo.lock"));
|
||||
let _ = fs::remove_file(start_dir.join("test_target/Cargo.lock"));
|
||||
let _ = fs::remove_dir_all(start_dir.join("sysroot"));
|
||||
|
||||
// Builds libs
|
||||
let mut rustflags = env
|
||||
.get("RUSTFLAGS")
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
if args.sysroot_panic_abort {
|
||||
let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default();
|
||||
if config.sysroot_panic_abort {
|
||||
rustflags.push_str(" -Cpanic=abort -Zpanic-abort-tests");
|
||||
}
|
||||
env.insert(
|
||||
"RUSTFLAGS".to_string(),
|
||||
format!("{} -Zmir-opt-level=3", rustflags),
|
||||
);
|
||||
let channel = if args.sysroot_release_channel {
|
||||
run_command_with_output_and_env(
|
||||
&[
|
||||
&"cargo",
|
||||
&"build",
|
||||
&"--target",
|
||||
&config.target,
|
||||
&"--release",
|
||||
],
|
||||
None,
|
||||
Some(&env),
|
||||
)?;
|
||||
rustflags.push_str(" -Z force-unstable-if-unmarked");
|
||||
let mut env = env.clone();
|
||||
|
||||
let mut args: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &"build", &"--target", &config.target];
|
||||
|
||||
if config.no_default_features {
|
||||
rustflags.push_str(" -Csymbol-mangling-version=v0");
|
||||
args.push(&"--no-default-features");
|
||||
}
|
||||
|
||||
let channel = if config.sysroot_release_channel {
|
||||
rustflags.push_str(" -Zmir-opt-level=3");
|
||||
args.push(&"--release");
|
||||
"release"
|
||||
} else {
|
||||
run_command_with_output_and_env(
|
||||
&[
|
||||
&"cargo",
|
||||
&"build",
|
||||
&"--target",
|
||||
&config.target,
|
||||
],
|
||||
None,
|
||||
Some(env),
|
||||
)?;
|
||||
"debug"
|
||||
};
|
||||
|
||||
env.insert("RUSTFLAGS".to_string(), rustflags);
|
||||
run_command_with_output_and_env(&args, Some(start_dir), Some(&env))?;
|
||||
|
||||
// Copy files to sysroot
|
||||
let sysroot_path = format!("sysroot/lib/rustlib/{}/lib/", config.target_triple);
|
||||
fs::create_dir_all(&sysroot_path)
|
||||
.map_err(|error| format!("Failed to create directory `{}`: {:?}", sysroot_path, error))?;
|
||||
let sysroot_path = start_dir.join(format!("sysroot/lib/rustlib/{}/lib/", config.target_triple));
|
||||
fs::create_dir_all(&sysroot_path).map_err(|error| {
|
||||
format!(
|
||||
"Failed to create directory `{}`: {:?}",
|
||||
sysroot_path.display(),
|
||||
error
|
||||
)
|
||||
})?;
|
||||
let copier = |dir_to_copy: &Path| {
|
||||
// FIXME: should not use shell command!
|
||||
run_command(&[&"cp", &"-r", &dir_to_copy, &sysroot_path], None).map(|_| ())
|
||||
};
|
||||
walk_dir(
|
||||
&format!("target/{}/{}/deps", config.target_triple, channel),
|
||||
start_dir.join(&format!("target/{}/{}/deps", config.target_triple, channel)),
|
||||
copier,
|
||||
copier,
|
||||
)?;
|
||||
|
||||
// Copy the source files to the sysroot (Rust for Linux needs this).
|
||||
let sysroot_src_path = "sysroot/lib/rustlib/src/rust";
|
||||
fs::create_dir_all(&sysroot_src_path)
|
||||
.map_err(|error| format!("Failed to create directory `{}`: {:?}", sysroot_src_path, error))?;
|
||||
run_command(&[&"cp", &"-r", &"sysroot_src/library/", &sysroot_src_path], None)?;
|
||||
let sysroot_src_path = start_dir.join("sysroot/lib/rustlib/src/rust");
|
||||
fs::create_dir_all(&sysroot_src_path).map_err(|error| {
|
||||
format!(
|
||||
"Failed to create directory `{}`: {:?}",
|
||||
sysroot_src_path.display(),
|
||||
error
|
||||
)
|
||||
})?;
|
||||
run_command(
|
||||
&[
|
||||
&"cp",
|
||||
&"-r",
|
||||
&start_dir.join("sysroot_src/library/"),
|
||||
&sysroot_src_path,
|
||||
],
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_codegen(args: &BuildArg) -> Result<(), String> {
|
||||
fn build_codegen(args: &mut BuildArg) -> Result<(), String> {
|
||||
let mut env = HashMap::new();
|
||||
|
||||
env.insert("LD_LIBRARY_PATH".to_string(), args.gcc_path.clone());
|
||||
env.insert("LIBRARY_PATH".to_string(), args.gcc_path.clone());
|
||||
env.insert(
|
||||
"LD_LIBRARY_PATH".to_string(),
|
||||
args.config_info.gcc_path.clone(),
|
||||
);
|
||||
env.insert(
|
||||
"LIBRARY_PATH".to_string(),
|
||||
args.config_info.gcc_path.clone(),
|
||||
);
|
||||
|
||||
if args.config_info.no_default_features {
|
||||
env.insert(
|
||||
"RUSTFLAGS".to_string(),
|
||||
"-Csymbol-mangling-version=v0".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut command: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &"rustc"];
|
||||
if args.codegen_release_channel {
|
||||
if args.config_info.channel == Channel::Release {
|
||||
command.push(&"--release");
|
||||
env.insert("CHANNEL".to_string(), "release".to_string());
|
||||
env.insert("CARGO_INCREMENTAL".to_string(), "1".to_string());
|
||||
} else {
|
||||
env.insert("CHANNEL".to_string(), "debug".to_string());
|
||||
}
|
||||
if args.config_info.no_default_features {
|
||||
command.push(&"--no-default-features");
|
||||
}
|
||||
let flags = args.flags.iter().map(|s| s.as_str()).collect::<Vec<_>>();
|
||||
for flag in &flags {
|
||||
command.push(flag);
|
||||
}
|
||||
run_command_with_output_and_env(&command, None, Some(&env))?;
|
||||
|
||||
let config = set_config(&mut env, &[], Some(&args.gcc_path))?;
|
||||
args.config_info.setup(&mut env, false)?;
|
||||
|
||||
// We voluntarily ignore the error.
|
||||
let _ = fs::remove_dir_all("target/out");
|
||||
|
@ -236,19 +217,16 @@ fn build_codegen(args: &BuildArg) -> Result<(), String> {
|
|||
})?;
|
||||
|
||||
println!("[BUILD] sysroot");
|
||||
build_sysroot(
|
||||
&mut env,
|
||||
args,
|
||||
&config,
|
||||
)?;
|
||||
build_sysroot(&env, &args.config_info)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), String> {
|
||||
let args = match BuildArg::new()? {
|
||||
let mut args = match BuildArg::new()? {
|
||||
Some(args) => args,
|
||||
None => return Ok(()),
|
||||
};
|
||||
build_codegen(&args)?;
|
||||
args.config_info.setup_gcc_path()?;
|
||||
build_codegen(&mut args)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
114
compiler/rustc_codegen_gcc/build_system/src/cargo.rs
Normal file
114
compiler/rustc_codegen_gcc/build_system/src/cargo.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
use crate::config::ConfigInfo;
|
||||
use crate::utils::{
|
||||
get_toolchain, run_command_with_output_and_env_no_err, rustc_toolchain_version_info,
|
||||
rustc_version_info,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn args() -> Result<Option<Vec<String>>, String> {
|
||||
// We skip the binary and the "cargo" option.
|
||||
if let Some("--help") = std::env::args().skip(2).next().as_deref() {
|
||||
usage();
|
||||
return Ok(None);
|
||||
}
|
||||
let args = std::env::args().skip(2).collect::<Vec<_>>();
|
||||
if args.is_empty() {
|
||||
return Err(
|
||||
"Expected at least one argument for `cargo` subcommand, found none".to_string(),
|
||||
);
|
||||
}
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
fn usage() {
|
||||
println!(
|
||||
r#"
|
||||
`cargo` command help:
|
||||
|
||||
[args] : Arguments to be passed to the cargo command
|
||||
--help : Show this help
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), String> {
|
||||
let args = match args()? {
|
||||
Some(a) => a,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// We first need to go to the original location to ensure that the config setup will go as
|
||||
// expected.
|
||||
let current_dir = std::env::current_dir()
|
||||
.and_then(|path| path.canonicalize())
|
||||
.map_err(|error| format!("Failed to get current directory path: {:?}", error))?;
|
||||
let current_exe = std::env::current_exe()
|
||||
.and_then(|path| path.canonicalize())
|
||||
.map_err(|error| format!("Failed to get current exe path: {:?}", error))?;
|
||||
let mut parent_dir = current_exe
|
||||
.components()
|
||||
.map(|comp| comp.as_os_str())
|
||||
.collect::<Vec<_>>();
|
||||
// We run this script from "build_system/target/release/y", so we need to remove these elements.
|
||||
for to_remove in &["y", "release", "target", "build_system"] {
|
||||
if parent_dir
|
||||
.last()
|
||||
.map(|part| part == to_remove)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
parent_dir.pop();
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Build script not executed from `build_system/target/release/y` (in path {})",
|
||||
current_exe.display(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let parent_dir = PathBuf::from(parent_dir.join(&OsStr::new("/")));
|
||||
std::env::set_current_dir(&parent_dir).map_err(|error| {
|
||||
format!(
|
||||
"Failed to go to `{}` folder: {:?}",
|
||||
parent_dir.display(),
|
||||
error
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut env: HashMap<String, String> = std::env::vars().collect();
|
||||
ConfigInfo::default().setup(&mut env, false)?;
|
||||
let toolchain = get_toolchain()?;
|
||||
|
||||
let toolchain_version = rustc_toolchain_version_info(&toolchain)?;
|
||||
let default_version = rustc_version_info(None)?;
|
||||
if toolchain_version != default_version {
|
||||
println!(
|
||||
"rustc_codegen_gcc is built for {} but the default rustc version is {}.",
|
||||
toolchain_version.short, default_version.short,
|
||||
);
|
||||
println!("Using {}.", toolchain_version.short);
|
||||
}
|
||||
|
||||
// We go back to the original folder since we now have set up everything we needed.
|
||||
std::env::set_current_dir(¤t_dir).map_err(|error| {
|
||||
format!(
|
||||
"Failed to go back to `{}` folder: {:?}",
|
||||
current_dir.display(),
|
||||
error
|
||||
)
|
||||
})?;
|
||||
|
||||
let rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default();
|
||||
env.insert("RUSTDOCFLAGS".to_string(), rustflags);
|
||||
let toolchain = format!("+{}", toolchain);
|
||||
let mut command: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &toolchain];
|
||||
for arg in &args {
|
||||
command.push(arg);
|
||||
}
|
||||
if run_command_with_output_and_env_no_err(&command, None, Some(&env)).is_err() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
82
compiler/rustc_codegen_gcc/build_system/src/clean.rs
Normal file
82
compiler/rustc_codegen_gcc/build_system/src/clean.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use crate::utils::{remove_file, run_command};
|
||||
|
||||
use std::fs::remove_dir_all;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default)]
|
||||
enum CleanArg {
|
||||
/// `clean all`
|
||||
All,
|
||||
/// `clean ui-tests`
|
||||
UiTests,
|
||||
/// `clean --help`
|
||||
#[default]
|
||||
Help,
|
||||
}
|
||||
|
||||
impl CleanArg {
|
||||
fn new() -> Result<Self, String> {
|
||||
// We skip the binary and the "clean" option.
|
||||
for arg in std::env::args().skip(2) {
|
||||
return match arg.as_str() {
|
||||
"all" => Ok(Self::All),
|
||||
"ui-tests" => Ok(Self::UiTests),
|
||||
"--help" => Ok(Self::Help),
|
||||
a => Err(format!("Unknown argument `{}`", a)),
|
||||
};
|
||||
}
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() {
|
||||
println!(
|
||||
r#"
|
||||
`clean` command help:
|
||||
|
||||
all : Clean all data
|
||||
ui-tests : Clean ui tests
|
||||
--help : Show this help
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
fn clean_all() -> Result<(), String> {
|
||||
let dirs_to_remove = [
|
||||
"target",
|
||||
"build_sysroot/sysroot",
|
||||
"build_sysroot/sysroot_src",
|
||||
"build_sysroot/target",
|
||||
];
|
||||
for dir in dirs_to_remove {
|
||||
let _ = remove_dir_all(dir);
|
||||
}
|
||||
let dirs_to_remove = ["regex", "rand", "simple-raytracer"];
|
||||
for dir in dirs_to_remove {
|
||||
let _ = remove_dir_all(Path::new(crate::BUILD_DIR).join(dir));
|
||||
}
|
||||
|
||||
let files_to_remove = ["build_sysroot/Cargo.lock", "perf.data", "perf.data.old"];
|
||||
|
||||
for file in files_to_remove {
|
||||
let _ = remove_file(file);
|
||||
}
|
||||
|
||||
println!("Successfully ran `clean all`");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean_ui_tests() -> Result<(), String> {
|
||||
let path = Path::new(crate::BUILD_DIR).join("rust/build/x86_64-unknown-linux-gnu/test/ui/");
|
||||
run_command(&[&"find", &path, &"-name", &"stamp", &"-delete"], None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), String> {
|
||||
match CleanArg::new()? {
|
||||
CleanArg::All => clean_all()?,
|
||||
CleanArg::UiTests => clean_ui_tests()?,
|
||||
CleanArg::Help => usage(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
79
compiler/rustc_codegen_gcc/build_system/src/clone_gcc.rs
Normal file
79
compiler/rustc_codegen_gcc/build_system/src/clone_gcc.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use crate::config::ConfigInfo;
|
||||
use crate::utils::{git_clone, run_command_with_output};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn show_usage() {
|
||||
println!(
|
||||
r#"
|
||||
`clone-gcc` command help:
|
||||
|
||||
--out-path : Location where the GCC repository will be cloned (default: `./gcc`)"#
|
||||
);
|
||||
ConfigInfo::show_usage();
|
||||
println!(" --help : Show this help");
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Args {
|
||||
out_path: PathBuf,
|
||||
config_info: ConfigInfo,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn new() -> Result<Option<Self>, String> {
|
||||
let mut command_args = Self::default();
|
||||
|
||||
let mut out_path = None;
|
||||
|
||||
// We skip binary name and the `clone-gcc` command.
|
||||
let mut args = std::env::args().skip(2);
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--out-path" => match args.next() {
|
||||
Some(path) if !path.is_empty() => out_path = Some(path),
|
||||
_ => {
|
||||
return Err("Expected an argument after `--out-path`, found nothing".into())
|
||||
}
|
||||
},
|
||||
"--help" => {
|
||||
show_usage();
|
||||
return Ok(None);
|
||||
}
|
||||
arg => {
|
||||
if !command_args.config_info.parse_argument(arg, &mut args)? {
|
||||
return Err(format!("Unknown option {}", arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
command_args.out_path = match out_path {
|
||||
Some(p) => p.into(),
|
||||
None => PathBuf::from("./gcc"),
|
||||
};
|
||||
return Ok(Some(command_args));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), String> {
|
||||
let Some(args) = Args::new()? else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let result = git_clone("https://github.com/antoyo/gcc", Some(&args.out_path), false)?;
|
||||
if result.ran_clone {
|
||||
let gcc_commit = args.config_info.get_gcc_commit()?;
|
||||
println!("Checking out GCC commit `{}`...", gcc_commit);
|
||||
run_command_with_output(
|
||||
&[&"git", &"checkout", &gcc_commit],
|
||||
Some(Path::new(&result.repo_dir)),
|
||||
)?;
|
||||
} else {
|
||||
println!(
|
||||
"There is already a GCC folder in `{}`, leaving things as is...",
|
||||
args.out_path.display()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -1,149 +1,560 @@
|
|||
use crate::utils::{get_gcc_path, get_os_name, get_rustc_host_triple};
|
||||
use crate::utils::{
|
||||
create_symlink, get_os_name, run_command_with_output, rustc_version_info, split_args,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::env as std_env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use boml::{types::TomlValue, Toml};
|
||||
|
||||
#[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)]
|
||||
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: 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,
|
||||
}
|
||||
|
||||
// Returns the beginning for the command line of rustc.
|
||||
pub fn set_config(
|
||||
env: &mut HashMap<String, String>,
|
||||
test_flags: &[String],
|
||||
gcc_path: Option<&str>,
|
||||
) -> Result<ConfigInfo, String> {
|
||||
env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string());
|
||||
|
||||
let gcc_path = match gcc_path {
|
||||
Some(path) => path.to_string(),
|
||||
None => get_gcc_path()?,
|
||||
};
|
||||
env.insert("GCC_PATH".to_string(), gcc_path.clone());
|
||||
|
||||
let os_name = get_os_name()?;
|
||||
let dylib_ext = match os_name.as_str() {
|
||||
"Linux" => "so",
|
||||
"Darwin" => "dylib",
|
||||
os => return Err(format!("unsupported OS `{}`", os)),
|
||||
};
|
||||
let host_triple = get_rustc_host_triple()?;
|
||||
let mut linker = None;
|
||||
let mut target_triple = host_triple.clone();
|
||||
let mut target = target_triple.clone();
|
||||
|
||||
// We skip binary name and the command.
|
||||
let mut args = std::env::args().skip(2);
|
||||
|
||||
let mut set_target_triple = false;
|
||||
let mut set_target = false;
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--target-triple" => {
|
||||
if let Some(arg) = args.next() {
|
||||
target_triple = arg;
|
||||
set_target_triple = true;
|
||||
} else {
|
||||
return Err(
|
||||
"Expected a value after `--target-triple`, found nothing".to_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 {
|
||||
"--target" => {
|
||||
if let Some(arg) = args.next() {
|
||||
target = arg;
|
||||
set_target = true;
|
||||
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`, found nothing".to_string()
|
||||
);
|
||||
"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,
|
||||
"--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())
|
||||
}
|
||||
},
|
||||
"--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() {
|
||||
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 {
|
||||
// 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();
|
||||
|
||||
let url = format!(
|
||||
"https://github.com/antoyo/gcc/releases/download/master-{}/libgccjit.so",
|
||||
commit,
|
||||
);
|
||||
|
||||
println!("Downloading `{}`...", url);
|
||||
download_gccjit(url, &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)),
|
||||
)?;
|
||||
}
|
||||
|
||||
self.gcc_path = output_dir.display().to_string();
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
if set_target_triple && !set_target {
|
||||
target = target_triple.clone();
|
||||
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"),
|
||||
};
|
||||
let ConfigFile {
|
||||
gcc_path,
|
||||
download_gccjit,
|
||||
} = ConfigFile::new(&config_file)?;
|
||||
|
||||
if let Some(true) = download_gccjit {
|
||||
self.download_gccjit_if_needed()?;
|
||||
return Ok(());
|
||||
}
|
||||
self.gcc_path = match gcc_path {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
return Err(format!(
|
||||
"missing `gcc-path` value from `{}`",
|
||||
config_file.display(),
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if host_triple != target_triple {
|
||||
linker = Some(format!("-Clinker={}-gcc", target_triple));
|
||||
}
|
||||
let current_dir =
|
||||
std_env::current_dir().map_err(|error| format!("`current_dir` failed: {:?}", error))?;
|
||||
let channel = if let Some(channel) = env.get("CHANNEL") {
|
||||
channel.as_str()
|
||||
} else {
|
||||
"debug"
|
||||
};
|
||||
let cg_backend_path = current_dir
|
||||
.join("target")
|
||||
.join(channel)
|
||||
.join(&format!("librustc_codegen_gcc.{}", dylib_ext));
|
||||
let sysroot_path = current_dir.join("build_sysroot/sysroot");
|
||||
let mut rustflags = Vec::new();
|
||||
if let Some(cg_rustflags) = env.get("CG_RUSTFLAGS") {
|
||||
rustflags.push(cg_rustflags.clone());
|
||||
}
|
||||
if let Some(linker) = linker {
|
||||
rustflags.push(linker.to_string());
|
||||
}
|
||||
rustflags.extend_from_slice(&[
|
||||
"-Csymbol-mangling-version=v0".to_string(),
|
||||
"-Cdebuginfo=2".to_string(),
|
||||
format!("-Zcodegen-backend={}", cg_backend_path.display()),
|
||||
"--sysroot".to_string(),
|
||||
sysroot_path.display().to_string(),
|
||||
]);
|
||||
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());
|
||||
|
||||
if self.gcc_path.is_empty() && !use_system_gcc {
|
||||
self.setup_gcc_path()?;
|
||||
}
|
||||
env.insert("GCC_PATH".to_string(), self.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 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.
|
||||
self.cg_backend_path = "gcc".to_string();
|
||||
|
||||
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.
|
||||
rustflags.push("-Cpanic=abort".to_string());
|
||||
} else {
|
||||
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("build_sysroot/sysroot")
|
||||
.display()
|
||||
.to_string();
|
||||
rustflags.extend_from_slice(&["--sysroot".to_string(), self.sysroot_path.clone()]);
|
||||
};
|
||||
|
||||
// This environment variable is useful in case we want to change options of rustc commands.
|
||||
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());
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
rustflags.extend_from_slice(test_flags);
|
||||
// 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(),
|
||||
"-Cdebuginfo=2".to_string(),
|
||||
format!("-Zcodegen-backend={}", self.cg_backend_path),
|
||||
]);
|
||||
|
||||
// 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.
|
||||
target = current_dir.join("target/out").display(),
|
||||
sysroot = sysroot.display(),
|
||||
gcc_path = self.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(),
|
||||
"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(())
|
||||
}
|
||||
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",
|
||||
target_triple
|
||||
));
|
||||
let ld_library_path = format!(
|
||||
"{target}:{sysroot}:{gcc_path}",
|
||||
target = current_dir.join("target/out").display(),
|
||||
sysroot = sysroot.display(),
|
||||
);
|
||||
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:{}", path));
|
||||
|
||||
let mut rustc_command = vec!["rustc".to_string()];
|
||||
rustc_command.extend_from_slice(&rustflags);
|
||||
rustc_command.extend_from_slice(&[
|
||||
"-L".to_string(),
|
||||
"crate=target/out".to_string(),
|
||||
"--out-dir".to_string(),
|
||||
"target/out".to_string(),
|
||||
]);
|
||||
Ok(ConfigInfo {
|
||||
target,
|
||||
target_triple,
|
||||
rustc_command,
|
||||
})
|
||||
pub fn show_usage() {
|
||||
println!(
|
||||
"\
|
||||
--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
|
||||
--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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn download_gccjit(
|
||||
url: String,
|
||||
output_dir: &Path,
|
||||
tempfile_name: String,
|
||||
with_progress_bar: bool,
|
||||
) -> Result<(), String> {
|
||||
// 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
|
||||
}
|
||||
|
|
19
compiler/rustc_codegen_gcc/build_system/src/info.rs
Normal file
19
compiler/rustc_codegen_gcc/build_system/src/info.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use crate::config::ConfigInfo;
|
||||
|
||||
pub fn run() -> Result<(), String> {
|
||||
let mut config = ConfigInfo::default();
|
||||
|
||||
// We skip binary name and the `info` command.
|
||||
let mut args = std::env::args().skip(2);
|
||||
while let Some(arg) = args.next() {
|
||||
if arg == "--help" {
|
||||
println!("Display the path where the libgccjit will be located");
|
||||
return Ok(());
|
||||
}
|
||||
config.parse_argument(&arg, &mut args)?;
|
||||
}
|
||||
config.no_download = true;
|
||||
config.setup_gcc_path()?;
|
||||
println!("{}", config.gcc_path);
|
||||
Ok(())
|
||||
}
|
|
@ -2,12 +2,18 @@ use std::env;
|
|||
use std::process;
|
||||
|
||||
mod build;
|
||||
mod cargo;
|
||||
mod clean;
|
||||
mod clone_gcc;
|
||||
mod config;
|
||||
mod info;
|
||||
mod prepare;
|
||||
mod rustc_info;
|
||||
mod test;
|
||||
mod utils;
|
||||
|
||||
const BUILD_DIR: &str = "build";
|
||||
|
||||
macro_rules! arg_error {
|
||||
($($err:tt)*) => {{
|
||||
eprintln!($($err)*);
|
||||
|
@ -22,17 +28,25 @@ fn usage() {
|
|||
"\
|
||||
Available commands for build_system:
|
||||
|
||||
prepare : Run prepare command
|
||||
build : Run build command
|
||||
test : Run test command
|
||||
--help : Show this message"
|
||||
cargo : Run cargo command
|
||||
clean : Run clean command
|
||||
prepare : Run prepare command
|
||||
build : Run build command
|
||||
test : Run test command
|
||||
info : Run info command
|
||||
clone-gcc : Run clone-gcc command
|
||||
--help : Show this message"
|
||||
);
|
||||
}
|
||||
|
||||
pub enum Command {
|
||||
Cargo,
|
||||
Clean,
|
||||
CloneGcc,
|
||||
Prepare,
|
||||
Build,
|
||||
Test,
|
||||
Info,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -41,9 +55,13 @@ fn main() {
|
|||
}
|
||||
|
||||
let command = match env::args().nth(1).as_deref() {
|
||||
Some("cargo") => Command::Cargo,
|
||||
Some("clean") => Command::Clean,
|
||||
Some("prepare") => Command::Prepare,
|
||||
Some("build") => Command::Build,
|
||||
Some("test") => Command::Test,
|
||||
Some("info") => Command::Info,
|
||||
Some("clone-gcc") => Command::CloneGcc,
|
||||
Some("--help") => {
|
||||
usage();
|
||||
process::exit(0);
|
||||
|
@ -57,11 +75,15 @@ fn main() {
|
|||
};
|
||||
|
||||
if let Err(e) = match command {
|
||||
Command::Cargo => cargo::run(),
|
||||
Command::Clean => clean::run(),
|
||||
Command::Prepare => prepare::run(),
|
||||
Command::Build => build::run(),
|
||||
Command::Test => test::run(),
|
||||
Command::Info => info::run(),
|
||||
Command::CloneGcc => clone_gcc::run(),
|
||||
} {
|
||||
eprintln!("Command failed to run: {e:?}");
|
||||
eprintln!("Command failed to run: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
use crate::rustc_info::get_rustc_path;
|
||||
use crate::utils::{cargo_install, git_clone, run_command, run_command_with_output, walk_dir};
|
||||
use crate::utils::{
|
||||
cargo_install, git_clone_root_dir, remove_file, run_command, run_command_with_output, walk_dir,
|
||||
};
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
fn prepare_libcore(sysroot_path: &Path, libgccjit12_patches: bool, cross_compile: bool) -> Result<(), String> {
|
||||
fn prepare_libcore(
|
||||
sysroot_path: &Path,
|
||||
libgccjit12_patches: bool,
|
||||
cross_compile: bool,
|
||||
) -> Result<(), String> {
|
||||
let rustc_path = match get_rustc_path() {
|
||||
Some(path) => path,
|
||||
None => return Err("`rustc` path not found".to_string()),
|
||||
|
@ -88,10 +94,14 @@ fn prepare_libcore(sysroot_path: &Path, libgccjit12_patches: bool, cross_compile
|
|||
},
|
||||
)?;
|
||||
if cross_compile {
|
||||
walk_dir("cross_patches", |_| Ok(()), |file_path: &Path| {
|
||||
patches.push(file_path.to_path_buf());
|
||||
Ok(())
|
||||
})?;
|
||||
walk_dir(
|
||||
"patches/cross_patches",
|
||||
|_| Ok(()),
|
||||
|file_path: &Path| {
|
||||
patches.push(file_path.to_path_buf());
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
}
|
||||
if libgccjit12_patches {
|
||||
walk_dir(
|
||||
|
@ -121,6 +131,30 @@ fn prepare_libcore(sysroot_path: &Path, libgccjit12_patches: bool, cross_compile
|
|||
)?;
|
||||
}
|
||||
println!("Successfully prepared libcore for building");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: remove when we can ignore warnings in rustdoc tests.
|
||||
fn prepare_rand() -> Result<(), String> {
|
||||
// Apply patch for the rand crate.
|
||||
let file_path = "patches/crates/0001-Remove-deny-warnings.patch";
|
||||
let rand_dir = Path::new("build/rand");
|
||||
println!("[GIT] apply `{}`", file_path);
|
||||
let path = Path::new("../..").join(file_path);
|
||||
run_command_with_output(&[&"git", &"apply", &path], Some(rand_dir))?;
|
||||
run_command_with_output(&[&"git", &"add", &"-A"], Some(rand_dir))?;
|
||||
run_command_with_output(
|
||||
&[
|
||||
&"git",
|
||||
&"commit",
|
||||
&"--no-gpg-sign",
|
||||
&"-m",
|
||||
&format!("Patch {}", path.display()),
|
||||
],
|
||||
Some(rand_dir),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -129,8 +163,7 @@ fn build_raytracer(repo_dir: &Path) -> Result<(), String> {
|
|||
run_command(&[&"cargo", &"build"], Some(repo_dir))?;
|
||||
let mv_target = repo_dir.join("raytracer_cg_llvm");
|
||||
if mv_target.is_file() {
|
||||
std::fs::remove_file(&mv_target)
|
||||
.map_err(|e| format!("Failed to remove file `{}`: {e:?}", mv_target.display()))?;
|
||||
remove_file(&mv_target)?;
|
||||
}
|
||||
run_command(
|
||||
&[&"mv", &"target/debug/main", &"raytracer_cg_llvm"],
|
||||
|
@ -143,28 +176,13 @@ fn clone_and_setup<F>(repo_url: &str, checkout_commit: &str, extra: Option<F>) -
|
|||
where
|
||||
F: Fn(&Path) -> Result<(), String>,
|
||||
{
|
||||
let clone_result = git_clone(repo_url, None)?;
|
||||
let clone_result = git_clone_root_dir(repo_url, &Path::new(crate::BUILD_DIR), false)?;
|
||||
if !clone_result.ran_clone {
|
||||
println!("`{}` has already been cloned", clone_result.repo_name);
|
||||
}
|
||||
let repo_path = Path::new(&clone_result.repo_name);
|
||||
let repo_path = Path::new(crate::BUILD_DIR).join(&clone_result.repo_name);
|
||||
run_command(&[&"git", &"checkout", &"--", &"."], Some(&repo_path))?;
|
||||
run_command(&[&"git", &"checkout", &checkout_commit], Some(&repo_path))?;
|
||||
let filter = format!("-{}-", clone_result.repo_name);
|
||||
walk_dir(
|
||||
"crate_patches",
|
||||
|_| Ok(()),
|
||||
|file_path| {
|
||||
let patch = file_path.as_os_str().to_str().unwrap();
|
||||
if patch.contains(&filter) && patch.ends_with(".patch") {
|
||||
run_command_with_output(
|
||||
&[&"git", &"am", &file_path.canonicalize().unwrap()],
|
||||
Some(&repo_path),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
if let Some(extra) = extra {
|
||||
extra(&repo_path)?;
|
||||
}
|
||||
|
@ -210,8 +228,7 @@ impl PrepareArg {
|
|||
--only-libcore : Only setup libcore and don't clone other repositories
|
||||
--cross : Apply the patches needed to do cross-compilation
|
||||
--libgccjit12-patches : Apply patches needed for libgccjit12
|
||||
--help : Show this help
|
||||
"#
|
||||
--help : Show this help"#
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +247,7 @@ pub fn run() -> Result<(), String> {
|
|||
let to_clone = &[
|
||||
(
|
||||
"https://github.com/rust-random/rand.git",
|
||||
"0f933f9c7176e53b2a3c7952ded484e1783f0bf1",
|
||||
"1f4507a8e1cf8050e4ceef95eeda8f64645b6719",
|
||||
None,
|
||||
),
|
||||
(
|
||||
|
@ -248,6 +265,8 @@ pub fn run() -> Result<(), String> {
|
|||
for (repo_url, checkout_commit, cb) in to_clone {
|
||||
clone_and_setup(repo_url, checkout_commit, *cb)?;
|
||||
}
|
||||
|
||||
prepare_rand()?;
|
||||
}
|
||||
|
||||
println!("Successfully ran `prepare`");
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||
use std::ffi::OsStr;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitStatus, Output};
|
||||
|
||||
fn get_command_inner(
|
||||
|
@ -29,22 +29,40 @@ fn check_exit_status(
|
|||
input: &[&dyn AsRef<OsStr>],
|
||||
cwd: Option<&Path>,
|
||||
exit_status: ExitStatus,
|
||||
output: Option<&Output>,
|
||||
show_err: bool,
|
||||
) -> Result<(), String> {
|
||||
if exit_status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Command `{}`{} exited with status {:?}",
|
||||
input
|
||||
.iter()
|
||||
.map(|s| s.as_ref().to_str().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
cwd.map(|cwd| format!(" (running in folder `{}`)", cwd.display()))
|
||||
.unwrap_or_default(),
|
||||
exit_status.code(),
|
||||
))
|
||||
return Ok(());
|
||||
}
|
||||
let mut error = format!(
|
||||
"Command `{}`{} exited with status {:?}",
|
||||
input
|
||||
.iter()
|
||||
.map(|s| s.as_ref().to_str().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
cwd.map(|cwd| format!(" (running in folder `{}`)", cwd.display()))
|
||||
.unwrap_or_default(),
|
||||
exit_status.code()
|
||||
);
|
||||
let input = input.iter().map(|i| i.as_ref()).collect::<Vec<&OsStr>>();
|
||||
if show_err {
|
||||
eprintln!("Command `{:?}` failed", input);
|
||||
}
|
||||
if let Some(output) = output {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
if !stdout.is_empty() {
|
||||
error.push_str("\n==== STDOUT ====\n");
|
||||
error.push_str(&*stdout);
|
||||
}
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if !stderr.is_empty() {
|
||||
error.push_str("\n==== STDERR ====\n");
|
||||
error.push_str(&*stderr);
|
||||
}
|
||||
}
|
||||
Err(error)
|
||||
}
|
||||
|
||||
fn command_error<D: Debug>(input: &[&dyn AsRef<OsStr>], cwd: &Option<&Path>, error: D) -> String {
|
||||
|
@ -73,7 +91,7 @@ pub fn run_command_with_env(
|
|||
let output = get_command_inner(input, cwd, env)
|
||||
.output()
|
||||
.map_err(|e| command_error(input, &cwd, e))?;
|
||||
check_exit_status(input, cwd, output.status)?;
|
||||
check_exit_status(input, cwd, output.status, Some(&output), true)?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
|
@ -86,7 +104,7 @@ pub fn run_command_with_output(
|
|||
.map_err(|e| command_error(input, &cwd, e))?
|
||||
.wait()
|
||||
.map_err(|e| command_error(input, &cwd, e))?;
|
||||
check_exit_status(input, cwd, exit_status)?;
|
||||
check_exit_status(input, cwd, exit_status, None, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -100,7 +118,21 @@ pub fn run_command_with_output_and_env(
|
|||
.map_err(|e| command_error(input, &cwd, e))?
|
||||
.wait()
|
||||
.map_err(|e| command_error(input, &cwd, e))?;
|
||||
check_exit_status(input, cwd, exit_status)?;
|
||||
check_exit_status(input, cwd, exit_status, None, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_command_with_output_and_env_no_err(
|
||||
input: &[&dyn AsRef<OsStr>],
|
||||
cwd: Option<&Path>,
|
||||
env: Option<&HashMap<String, String>>,
|
||||
) -> Result<(), String> {
|
||||
let exit_status = get_command_inner(input, cwd, env)
|
||||
.spawn()
|
||||
.map_err(|e| command_error(input, &cwd, e))?
|
||||
.wait()
|
||||
.map_err(|e| command_error(input, &cwd, e))?;
|
||||
check_exit_status(input, cwd, exit_status, None, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -143,80 +175,157 @@ pub fn get_os_name() -> Result<String, String> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_rustc_host_triple() -> Result<String, String> {
|
||||
let output = run_command(&[&"rustc", &"-vV"], None)?;
|
||||
let content = std::str::from_utf8(&output.stdout).unwrap_or("");
|
||||
|
||||
for line in content.split('\n').map(|line| line.trim()) {
|
||||
if !line.starts_with("host:") {
|
||||
continue;
|
||||
}
|
||||
return Ok(line.split(':').nth(1).unwrap().trim().to_string());
|
||||
}
|
||||
Err("Cannot find host triple".to_string())
|
||||
#[derive(Default, PartialEq)]
|
||||
pub struct RustcVersionInfo {
|
||||
pub short: String,
|
||||
pub version: String,
|
||||
pub host: Option<String>,
|
||||
pub commit_hash: Option<String>,
|
||||
pub commit_date: Option<String>,
|
||||
}
|
||||
|
||||
pub fn get_gcc_path() -> Result<String, String> {
|
||||
let content = match fs::read_to_string("gcc_path") {
|
||||
Ok(content) => content,
|
||||
Err(_) => {
|
||||
return Err(
|
||||
"Please put the path to your custom build of libgccjit in the file \
|
||||
`gcc_path`, see Readme.md for details"
|
||||
.into(),
|
||||
)
|
||||
pub fn rustc_toolchain_version_info(toolchain: &str) -> Result<RustcVersionInfo, String> {
|
||||
rustc_version_info_inner(None, Some(toolchain))
|
||||
}
|
||||
|
||||
pub fn rustc_version_info(rustc: Option<&str>) -> Result<RustcVersionInfo, String> {
|
||||
rustc_version_info_inner(rustc, None)
|
||||
}
|
||||
|
||||
fn rustc_version_info_inner(
|
||||
rustc: Option<&str>,
|
||||
toolchain: Option<&str>,
|
||||
) -> Result<RustcVersionInfo, String> {
|
||||
let output = if let Some(toolchain) = toolchain {
|
||||
run_command(&[&rustc.unwrap_or("rustc"), &toolchain, &"-vV"], None)
|
||||
} else {
|
||||
run_command(&[&rustc.unwrap_or("rustc"), &"-vV"], None)
|
||||
}?;
|
||||
let content = std::str::from_utf8(&output.stdout).unwrap_or("");
|
||||
|
||||
let mut info = RustcVersionInfo::default();
|
||||
let mut lines = content.split('\n');
|
||||
info.short = match lines.next() {
|
||||
Some(s) => s.to_string(),
|
||||
None => return Err("failed to retrieve rustc version".to_string()),
|
||||
};
|
||||
|
||||
for line in lines.map(|line| line.trim()) {
|
||||
match line.split_once(':') {
|
||||
Some(("host", data)) => info.host = Some(data.trim().to_string()),
|
||||
Some(("release", data)) => info.version = data.trim().to_string(),
|
||||
Some(("commit-hash", data)) => info.commit_hash = Some(data.trim().to_string()),
|
||||
Some(("commit-date", data)) => info.commit_date = Some(data.trim().to_string()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if info.version.is_empty() {
|
||||
Err("failed to retrieve rustc version".to_string())
|
||||
} else {
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_toolchain() -> Result<String, String> {
|
||||
let content = match fs::read_to_string("rust-toolchain") {
|
||||
Ok(content) => content,
|
||||
Err(_) => return Err("No `rust-toolchain` file found".to_string()),
|
||||
};
|
||||
match content
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.filter(|line| !line.is_empty())
|
||||
.filter_map(|line| {
|
||||
if !line.starts_with("channel") {
|
||||
return None;
|
||||
}
|
||||
line.split('"').skip(1).next()
|
||||
})
|
||||
.next()
|
||||
{
|
||||
Some(gcc_path) => {
|
||||
let path = Path::new(gcc_path);
|
||||
if !path.exists() {
|
||||
Err(format!(
|
||||
"Path `{}` contained in the `gcc_path` file doesn't exist",
|
||||
gcc_path,
|
||||
))
|
||||
} else {
|
||||
Ok(gcc_path.into())
|
||||
}
|
||||
}
|
||||
None => Err("No path found in `gcc_path` file".into()),
|
||||
Some(toolchain) => Ok(toolchain.to_string()),
|
||||
None => Err("Couldn't find `channel` in `rust-toolchain` file".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CloneResult {
|
||||
pub ran_clone: bool,
|
||||
pub repo_name: String,
|
||||
pub repo_dir: String,
|
||||
}
|
||||
|
||||
pub fn git_clone(to_clone: &str, dest: Option<&Path>) -> Result<CloneResult, String> {
|
||||
let repo_name = to_clone.split('/').last().unwrap();
|
||||
let repo_name = match repo_name.strip_suffix(".git") {
|
||||
Some(n) => n.to_string(),
|
||||
None => repo_name.to_string(),
|
||||
};
|
||||
|
||||
let dest = dest
|
||||
.map(|dest| dest.join(&repo_name))
|
||||
.unwrap_or_else(|| Path::new(&repo_name).into());
|
||||
fn git_clone_inner(
|
||||
to_clone: &str,
|
||||
dest: &Path,
|
||||
shallow_clone: bool,
|
||||
repo_name: String,
|
||||
) -> Result<CloneResult, String> {
|
||||
if dest.is_dir() {
|
||||
return Ok(CloneResult {
|
||||
ran_clone: false,
|
||||
repo_name,
|
||||
repo_dir: dest.display().to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
run_command_with_output(&[&"git", &"clone", &to_clone, &dest], None)?;
|
||||
let mut command: Vec<&dyn AsRef<OsStr>> = vec![&"git", &"clone", &to_clone, &dest];
|
||||
if shallow_clone {
|
||||
command.push(&"--depth");
|
||||
command.push(&"1");
|
||||
}
|
||||
run_command_with_output(&command, None)?;
|
||||
Ok(CloneResult {
|
||||
ran_clone: true,
|
||||
repo_name,
|
||||
repo_dir: dest.display().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_repo_name(url: &str) -> String {
|
||||
let repo_name = url.split('/').last().unwrap();
|
||||
match repo_name.strip_suffix(".git") {
|
||||
Some(n) => n.to_string(),
|
||||
None => repo_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn git_clone(
|
||||
to_clone: &str,
|
||||
dest: Option<&Path>,
|
||||
shallow_clone: bool,
|
||||
) -> Result<CloneResult, String> {
|
||||
let repo_name = get_repo_name(to_clone);
|
||||
let tmp: PathBuf;
|
||||
|
||||
let dest = match dest {
|
||||
Some(dest) => dest,
|
||||
None => {
|
||||
tmp = repo_name.clone().into();
|
||||
&tmp
|
||||
}
|
||||
};
|
||||
git_clone_inner(to_clone, dest, shallow_clone, repo_name)
|
||||
}
|
||||
|
||||
/// This function differs from `git_clone` in how it handles *where* the repository will be cloned.
|
||||
/// In `git_clone`, it is cloned in the provided path. In this function, the path you provide is
|
||||
/// the parent folder. So if you pass "a" as folder and try to clone "b.git", it will be cloned into
|
||||
/// `a/b`.
|
||||
pub fn git_clone_root_dir(
|
||||
to_clone: &str,
|
||||
dest_parent_dir: &Path,
|
||||
shallow_clone: bool,
|
||||
) -> Result<CloneResult, String> {
|
||||
let repo_name = get_repo_name(to_clone);
|
||||
|
||||
git_clone_inner(
|
||||
to_clone,
|
||||
&dest_parent_dir.join(&repo_name),
|
||||
shallow_clone,
|
||||
repo_name,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn walk_dir<P, D, F>(dir: P, mut dir_cb: D, mut file_cb: F) -> Result<(), String>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
|
@ -238,3 +347,105 @@ where
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn split_args(args: &str) -> Result<Vec<String>, String> {
|
||||
let mut out = Vec::new();
|
||||
let mut start = 0;
|
||||
let args = args.trim();
|
||||
let mut iter = args.char_indices().peekable();
|
||||
|
||||
while let Some((pos, c)) = iter.next() {
|
||||
if c == ' ' {
|
||||
out.push(args[start..pos].to_string());
|
||||
let mut found_start = false;
|
||||
while let Some((pos, c)) = iter.peek() {
|
||||
if *c != ' ' {
|
||||
start = *pos;
|
||||
found_start = true;
|
||||
break;
|
||||
} else {
|
||||
iter.next();
|
||||
}
|
||||
}
|
||||
if !found_start {
|
||||
return Ok(out);
|
||||
}
|
||||
} else if c == '"' || c == '\'' {
|
||||
let end = c;
|
||||
let mut found_end = false;
|
||||
while let Some((_, c)) = iter.next() {
|
||||
if c == end {
|
||||
found_end = true;
|
||||
break;
|
||||
} else if c == '\\' {
|
||||
// We skip the escaped character.
|
||||
iter.next();
|
||||
}
|
||||
}
|
||||
if !found_end {
|
||||
return Err(format!(
|
||||
"Didn't find `{}` at the end of `{}`",
|
||||
end,
|
||||
&args[start..]
|
||||
));
|
||||
}
|
||||
} else if c == '\\' {
|
||||
// We skip the escaped character.
|
||||
iter.next();
|
||||
}
|
||||
}
|
||||
let s = args[start..].trim();
|
||||
if !s.is_empty() {
|
||||
out.push(s.to_string());
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn remove_file<P: AsRef<Path> + ?Sized>(file_path: &P) -> Result<(), String> {
|
||||
std::fs::remove_file(file_path).map_err(|error| {
|
||||
format!(
|
||||
"Failed to remove `{}`: {:?}",
|
||||
file_path.as_ref().display(),
|
||||
error
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> Result<(), String> {
|
||||
#[cfg(windows)]
|
||||
let symlink = std::os::windows::fs::symlink_file;
|
||||
#[cfg(not(windows))]
|
||||
let symlink = std::os::unix::fs::symlink;
|
||||
|
||||
symlink(&original, &link).map_err(|err| {
|
||||
format!(
|
||||
"failed to create a symlink `{}` to `{}`: {:?}",
|
||||
original.as_ref().display(),
|
||||
link.as_ref().display(),
|
||||
err,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_split_args() {
|
||||
// Missing `"` at the end.
|
||||
assert!(split_args("\"tada").is_err());
|
||||
// Missing `'` at the end.
|
||||
assert!(split_args("\'tada").is_err());
|
||||
|
||||
assert_eq!(
|
||||
split_args("a \"b\" c"),
|
||||
Ok(vec!["a".to_string(), "\"b\"".to_string(), "c".to_string()])
|
||||
);
|
||||
// Trailing whitespace characters.
|
||||
assert_eq!(
|
||||
split_args(" a \"b\" c "),
|
||||
Ok(vec!["a".to_string(), "\"b\"".to_string(), "c".to_string()])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue