about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2023-11-08 17:31:56 +0100
committerGuillaume Gomez <guillaume1.gomez@gmail.com>2023-12-04 15:28:53 +0100
commit7b76ac4eb74f3cf8e0d616b82f8135fd7fd7ccab (patch)
tree29bb72336f1a07686f3cb01a337451f409a2c7a8
parent04f32f2016495ec8aabf6ba00b47a5665811eae6 (diff)
downloadrust-7b76ac4eb74f3cf8e0d616b82f8135fd7fd7ccab.tar.gz
rust-7b76ac4eb74f3cf8e0d616b82f8135fd7fd7ccab.zip
Rustify `test.sh`
-rw-r--r--build_system/src/build.rs128
-rw-r--r--build_system/src/config.rs332
-rw-r--r--build_system/src/prepare.rs18
-rw-r--r--build_system/src/test.rs1141
-rw-r--r--build_system/src/utils.rs104
5 files changed, 1518 insertions, 205 deletions
diff --git a/build_system/src/build.rs b/build_system/src/build.rs
index f1c3701a946..6390458d4fd 100644
--- a/build_system/src/build.rs
+++ b/build_system/src/build.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::ConfigInfo;
+use crate::utils::{get_gcc_path, run_command, run_command_with_output_and_env, walk_dir};
 use std::collections::HashMap;
 use std::ffi::OsStr;
 use std::fs;
@@ -10,10 +8,9 @@ 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 {
@@ -29,13 +26,9 @@ impl BuildArg {
         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());
@@ -63,12 +56,14 @@ impl BuildArg {
                     if args.next().is_some() {
                         // Handled in config.rs.
                     } else {
-                        return Err(
-                            "Expected a value after `--target`, found nothing".to_string()
-                        );
+                        return Err("Expected a value after `--target`, found nothing".to_string());
+                    }
+                }
+                arg => {
+                    if !build_arg.config_info.parse_argument(arg, &mut args)? {
+                        return Err(format!("Unknown argument `{}`", arg));
                     }
                 }
-                arg => return Err(format!("Unknown argument `{}`", arg)),
             }
         }
         Ok(Some(build_arg))
@@ -80,28 +75,26 @@ impl BuildArg {
 `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,
+fn build_sysroot_inner(
+    env: &HashMap<String, String>,
+    sysroot_panic_abort: bool,
+    sysroot_release_channel: bool,
     config: &ConfigInfo,
+    start_dir: Option<&Path>,
 ) -> Result<(), String> {
-    std::env::set_current_dir("build_sysroot")
-        .map_err(|error| format!("Failed to go to `build_sysroot` directory: {:?}", error))?;
+    let start_dir = start_dir.unwrap_or_else(|| Path::new("."));
     // 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,23 +131,22 @@ 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 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 {
+    rustflags.push_str(" -Z force-unstable-if-unmarked");
+    let channel = if sysroot_release_channel {
+        let mut env = env.clone();
+        env.insert(
+            "RUSTFLAGS".to_string(),
+            format!("{} -Zmir-opt-level=3", rustflags),
+        );
         run_command_with_output_and_env(
             &[
                 &"cargo",
@@ -163,33 +155,34 @@ fn build_sysroot(
                 &config.target,
                 &"--release",
             ],
-            None,
+            Some(start_dir),
             Some(&env),
         )?;
         "release"
     } else {
         run_command_with_output_and_env(
-            &[
-                &"cargo",
-                &"build",
-                &"--target",
-                &config.target,
-            ],
-            None,
+            &[&"cargo", &"build", &"--target", &config.target],
+            Some(start_dir),
             Some(env),
         )?;
         "debug"
     };
 
     // 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,
     )?;
@@ -203,7 +196,22 @@ fn build_sysroot(
     Ok(())
 }
 
-fn build_codegen(args: &BuildArg) -> Result<(), String> {
+pub fn build_sysroot(
+    env: &HashMap<String, String>,
+    sysroot_panic_abort: bool,
+    sysroot_release_channel: bool,
+    config: &ConfigInfo,
+) -> Result<(), String> {
+    build_sysroot_inner(
+        env,
+        sysroot_panic_abort,
+        sysroot_release_channel,
+        config,
+        Some(Path::new("build_sysroot")),
+    )
+}
+
+fn build_codegen(args: &mut BuildArg) -> Result<(), String> {
     let mut env = HashMap::new();
 
     env.insert("LD_LIBRARY_PATH".to_string(), args.gcc_path.clone());
@@ -223,7 +231,8 @@ fn build_codegen(args: &BuildArg) -> Result<(), String> {
     }
     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, &[], Some(&args.gcc_path))?;
 
     // We voluntarily ignore the error.
     let _ = fs::remove_dir_all("target/out");
@@ -237,18 +246,19 @@ fn build_codegen(args: &BuildArg) -> Result<(), String> {
 
     println!("[BUILD] sysroot");
     build_sysroot(
-        &mut env,
-        args,
-        &config,
+        &env,
+        args.config_info.sysroot_panic_abort,
+        args.config_info.sysroot_release_channel,
+        &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)?;
+    build_codegen(&mut args)?;
     Ok(())
 }
diff --git a/build_system/src/config.rs b/build_system/src/config.rs
index 64d9bd73e01..763cac8edb6 100644
--- a/build_system/src/config.rs
+++ b/build_system/src/config.rs
@@ -1,149 +1,229 @@
-use crate::utils::{get_gcc_path, get_os_name, get_rustc_host_triple};
+use crate::utils::{get_gcc_path, get_os_name, rustc_version_info, split_args};
 use std::collections::HashMap;
 use std::env as std_env;
 
+#[derive(Default)]
 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 sysroot_panic_abort: 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 {
+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-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()
-                    );
+                    )
                 }
             },
-            "--target" => {
-                if let Some(arg) = args.next() {
-                    target = arg;
-                    set_target = true;
-                } else {
-                    return Err(
-                        "Expected a value after `--target`, found nothing".to_string()
-                    );
+            "--target" => match args.next() {
+                Some(arg) if !arg.is_empty() => self.target = arg.to_string(),
+                _ => return Err("Expected a value after `--target`, found nothing".to_string()),
+            },
+            "--out-dir" => match args.next() {
+                Some(arg) if !arg.is_empty() => {
+                    // env.insert("CARGO_TARGET_DIR".to_string(), arg.to_string());
+                    self.cargo_target_dir = arg.to_string();
                 }
+                _ => return Err("Expected a value after `--out-dir`, found nothing".to_string()),
             },
-            _ => (),
+            "--release-sysroot" => self.sysroot_release_channel = true,
+            "--sysroot-panic-abort" => self.sysroot_panic_abort = true,
+            _ => return Ok(false),
         }
+        Ok(true)
     }
 
-    if set_target_triple && !set_target {
-        target = target_triple.clone();
-    }
+    pub fn setup(
+        &mut self,
+        env: &mut HashMap<String, String>,
+        test_flags: &[String],
+        gcc_path: Option<&str>,
+    ) -> Result<(), String> {
+        env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string());
 
-    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(),
-    ]);
-
-    // 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" {
+        let gcc_path = match gcc_path {
+            Some(path) => path.to_string(),
+            None => get_gcc_path()?,
+        };
+        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 = rustc_version_info(Some(&rustc))?.host.unwrap_or_default();
+
+        if !self.target_triple.is_empty() && self.target.is_empty() {
+            self.target = self.target_triple.clone();
+        }
+        if self.target.is_empty() {
+            self.target = self.host_triple.clone();
+        }
+        if self.target_triple.is_empty() {
+            self.target_triple = self.host_triple.clone();
+        }
+
+        let mut linker = None;
+
+        if self.host_triple != self.target_triple {
+            if self.target_triple == "m68k-unknown-linux-gnu" {
+                linker = Some("-Clinker=m68k-unknown-linux-gnu-gcc".to_string());
+            } else if self.target_triple == "aarch64-unknown-linux-gnu" {
+                // We are cross-compiling for aarch64. Use the correct linker and run tests in qemu.
+                linker = Some("-Clinker=aarch64-linux-gnu-gcc".to_string());
+            } else {
+                return Err("Unknown non-native platform".to_string());
+            }
+
+            self.run_in_vm = true;
+        }
+
+        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 has_builtin_backend = env
+            .get("BUILTIN_BACKEND")
+            .map(|backend| !backend.is_empty())
+            .unwrap_or(false);
+        let cg_backend_path;
+
+        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.
+            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()]);
+                }
+                _ => {}
+            }
+            rustflags.push("-Cpanic=abort".to_string());
+        } else {
+            cg_backend_path = current_dir
+                .join("target")
+                .join(channel)
+                .join(&format!("librustc_codegen_gcc.{}", self.dylib_ext))
+                .display()
+                .to_string();
+            let sysroot_path = current_dir.join("build_sysroot/sysroot");
+            rustflags
+                .extend_from_slice(&["--sysroot".to_string(), sysroot_path.display().to_string()]);
+        };
+
+        if let Some(cg_rustflags) = env.get("CG_RUSTFLAGS") {
+            rustflags.extend_from_slice(&split_args(&cg_rustflags));
+        }
+        if let Some(linker) = linker {
+            rustflags.push(linker.to_string());
+        }
         rustflags.extend_from_slice(&[
-            "-Clink-arg=-undefined".to_string(),
-            "-Clink-arg=dynamic_lookup".to_string(),
+            "-Csymbol-mangling-version=v0".to_string(),
+            "-Cdebuginfo=2".to_string(),
+            format!("-Zcodegen-backend={}", 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());
+        }
+        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(),
+            ]);
+        }
+        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}",
+            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:/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 [arg]         : Set the target to [arg]
+    --target-triple [arg]  : Set the target triple to [arg]
+    --out-dir              : Location where the files will be generated
+    --release-sysroot      : Build sysroot in release mode
+    --sysroot-panic-abort  : Build the sysroot without unwinding support."
+        );
     }
-    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,
-    })
 }
diff --git a/build_system/src/prepare.rs b/build_system/src/prepare.rs
index 6c7c8586834..da9f8953ec3 100644
--- a/build_system/src/prepare.rs
+++ b/build_system/src/prepare.rs
@@ -4,7 +4,11 @@ use crate::utils::{cargo_install, git_clone, run_command, run_command_with_outpu
 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 +92,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(
+            "cross_patches",
+            |_| Ok(()),
+            |file_path: &Path| {
+                patches.push(file_path.to_path_buf());
+                Ok(())
+            },
+        )?;
     }
     if libgccjit12_patches {
         walk_dir(
diff --git a/build_system/src/test.rs b/build_system/src/test.rs
index 4c8c63e59ab..fb2b24da9a2 100644
--- a/build_system/src/test.rs
+++ b/build_system/src/test.rs
@@ -1,15 +1,1138 @@
-use crate::utils::run_command_with_output;
+use crate::build;
+use crate::config::ConfigInfo;
+use crate::utils::{
+    get_gcc_path, get_toolchain, run_command, run_command_with_env,
+    run_command_with_output_and_env, rustc_version_info, split_args, walk_dir,
+};
 
-fn get_args<'a>(args: &mut Vec<&'a dyn AsRef<std::ffi::OsStr>>, extra_args: &'a Vec<String>) {
-    for extra_arg in extra_args {
-        args.push(extra_arg);
+use std::collections::{BTreeSet, HashMap};
+use std::ffi::OsStr;
+use std::fs::remove_dir_all;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+type Env = HashMap<String, String>;
+type Runner = &'static dyn Fn(&Env, &TestArg) -> Result<(), String>;
+type Runners = HashMap<&'static str, (&'static str, Runner)>;
+
+fn get_runners() -> Runners {
+    let mut runners = HashMap::new();
+
+    runners.insert(
+        "--test-rustc",
+        ("Run all rustc tests", &test_rustc as Runner),
+    );
+    runners.insert(
+        "--test-successful-rustc",
+        ("Run successful rustc tests", &test_successful_rustc),
+    );
+    runners.insert(
+        "--test-failing-rustc",
+        ("Run failing rustc tests", &test_failing_rustc),
+    );
+    runners.insert("--test-libcore", ("Run libcore tests", &test_libcore));
+    runners.insert("--clean-ui-tests", ("Clean ui tests", &clean_ui_tests));
+    runners.insert("--clean", ("Empty cargo target directory", &clean));
+    runners.insert("--std-tests", ("Run std tests", &std_tests));
+    runners.insert("--asm-tests", ("Run asm tests", &asm_tests));
+    runners.insert(
+        "--extended-tests",
+        ("Run extended sysroot tests", &extended_sysroot_tests),
+    );
+    runners.insert(
+        "--extended-rand-tests",
+        ("Run extended rand tests", &extended_rand_tests),
+    );
+    runners.insert(
+        "--extended-regex-example-tests",
+        (
+            "Run extended regex example tests",
+            &extended_regex_example_tests,
+        ),
+    );
+    runners.insert(
+        "--extended-regex-tests",
+        ("Run extended regex tests", &extended_regex_tests),
+    );
+    runners.insert("--mini-tests", ("Run mini tests", &mini_tests));
+
+    runners
+}
+
+fn get_number_after_arg(
+    args: &mut impl Iterator<Item = String>,
+    option: &str,
+) -> Result<usize, String> {
+    match args.next() {
+        Some(nb) if !nb.is_empty() => match usize::from_str(&nb) {
+            Ok(nb) => Ok(nb),
+            Err(_) => Err(format!(
+                "Expected a number after `{}`, found `{}`",
+                option, nb
+            )),
+        },
+        _ => Err(format!(
+            "Expected a number after `{}`, found nothing",
+            option
+        )),
+    }
+}
+
+fn show_usage() {
+    println!(
+        r#"
+`test` 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]
+    --use-system-gcc       : Use system installed libgccjit
+    --build-only           : Only build rustc_codegen_gcc then exits
+    --use-backend          : Useful only for rustc testsuite
+    --nb-parts             : Used to split rustc_tests (for CI needs)
+    --current-part         : Used with `--nb-parts`, allows you to specify which parts to test"#
+    );
+    ConfigInfo::show_usage();
+    for (option, (doc, _)) in get_runners() {
+        let needed_spaces = 23_usize.saturating_sub(option.len());
+        let spaces: String = std::iter::repeat(' ').take(needed_spaces).collect();
+        println!("    {}{}: {}", option, spaces, doc);
     }
+    println!("    --help                 : Show this help");
+}
+
+#[derive(Default, PartialEq, Eq, Clone, Copy)]
+enum Channel {
+    #[default]
+    Debug,
+    Release,
+}
+
+impl Channel {
+    pub fn as_str(self) -> &'static str {
+        match self {
+            Self::Debug => "debug",
+            Self::Release => "release",
+        }
+    }
+}
+
+#[derive(Default)]
+struct TestArg {
+    no_default_features: bool,
+    build_only: bool,
+    gcc_path: String,
+    channel: Channel,
+    sysroot_channel: Channel,
+    use_backend: bool,
+    runners: BTreeSet<String>,
+    flags: Vec<String>,
+    backend: Option<String>,
+    nb_parts: Option<usize>,
+    current_part: Option<usize>,
+    sysroot_panic_abort: bool,
+    config_info: ConfigInfo,
+}
+
+impl TestArg {
+    fn new() -> Result<Option<Self>, String> {
+        let mut use_system_gcc = false;
+        let mut test_arg = Self::default();
+
+        // We skip binary name and the `test` command.
+        let mut args = std::env::args().skip(2);
+        let runners = get_runners();
+
+        while let Some(arg) = args.next() {
+            match arg.as_str() {
+                "--release" => test_arg.channel = Channel::Release,
+                "--release-sysroot" => test_arg.sysroot_channel = Channel::Release,
+                "--no-default-features" => {
+                    // To prevent adding it more than once.
+                    if !test_arg.no_default_features {
+                        test_arg.flags.push("--no-default-features".into());
+                    }
+                    test_arg.no_default_features = true;
+                }
+                "--features" => match args.next() {
+                    Some(feature) if !feature.is_empty() => {
+                        test_arg
+                            .flags
+                            .extend_from_slice(&["--features".into(), feature]);
+                    }
+                    _ => {
+                        return Err("Expected an argument after `--features`, found nothing".into())
+                    }
+                },
+                "--use-system-gcc" => use_system_gcc = true,
+                "--build-only" => test_arg.build_only = true,
+                "--use-backend" => match args.next() {
+                    Some(backend) if !backend.is_empty() => test_arg.backend = Some(backend),
+                    _ => {
+                        return Err(
+                            "Expected an argument after `--use-backend`, found nothing".into()
+                        )
+                    }
+                },
+                "--nb-parts" => {
+                    test_arg.nb_parts = Some(get_number_after_arg(&mut args, "--nb-parts")?);
+                }
+                "--current-part" => {
+                    test_arg.current_part =
+                        Some(get_number_after_arg(&mut args, "--current-part")?);
+                }
+                "--sysroot-panic-abort" => {
+                    test_arg.sysroot_panic_abort = true;
+                }
+                "--help" => {
+                    show_usage();
+                    return Ok(None);
+                }
+                x if runners.contains_key(x) => {
+                    test_arg.runners.insert(x.into());
+                }
+                arg => {
+                    if !test_arg.config_info.parse_argument(arg, &mut args)? {
+                        return Err(format!("Unknown option {}", arg));
+                    }
+                }
+            }
+
+            test_arg.gcc_path = if use_system_gcc {
+                println!("Using system GCC");
+                "gcc".to_string()
+            } else {
+                get_gcc_path()?
+            };
+        }
+        Ok(Some(test_arg))
+    }
+}
+
+fn build_if_no_backend(env: &Env, args: &TestArg) -> Result<(), String> {
+    if args.use_backend {
+        return Ok(());
+    }
+    let mut command: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &"rustc"];
+    if args.channel == Channel::Release {
+        let mut env = env.clone();
+        env.insert("CARGO_INCREMENTAL".to_string(), "1".to_string());
+        command.push(&"--release");
+        for flag in args.flags.iter() {
+            command.push(flag);
+        }
+        run_command_with_output_and_env(&command, None, Some(&env))
+    } else {
+        for flag in args.flags.iter() {
+            command.push(flag);
+        }
+        run_command_with_output_and_env(&command, None, Some(&env))
+    }
+}
+
+fn clean(_env: &Env, args: &TestArg) -> Result<(), String> {
+    let _ = std::fs::remove_dir_all(&args.config_info.cargo_target_dir);
+    let path = Path::new(&args.config_info.cargo_target_dir).join("gccjit");
+    std::fs::create_dir_all(&path)
+        .map_err(|error| format!("failed to create folder `{}`: {:?}", path.display(), error))
+}
+
+fn mini_tests(env: &Env, args: &TestArg) -> Result<(), String> {
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[BUILD] mini_core");
+    let crate_types = if args.config_info.host_triple != args.config_info.target_triple {
+        "lib"
+    } else {
+        "lib,dylib"
+    }
+    .to_string();
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/mini_core.rs",
+        &"--crate-name",
+        &"mini_core",
+        &"--crate-type",
+        &crate_types,
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_output_and_env(&command, None, Some(&env))?;
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[BUILD] example");
+    command.clear();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/example.rs",
+        &"--crate-type",
+        &"lib",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_output_and_env(&command, None, Some(&env))?;
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] mini_core_hello_world");
+    command.clear();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/mini_core_hello_world.rs",
+        &"--crate-name",
+        &"mini_core_hello_world",
+        &"--crate-type",
+        &"bin",
+        &"-g",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_output_and_env(&command, None, Some(&env))?;
+
+    let command: &[&dyn AsRef<OsStr>] = &[
+        &Path::new(&args.config_info.cargo_target_dir).join("mini_core_hello_world"),
+        &"abc",
+        &"bcd",
+    ];
+    run_command_in_vm(&command, env, args)?;
+    Ok(())
+}
+
+fn build_sysroot(env: &Env, args: &TestArg) -> Result<(), String> {
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[BUILD] sysroot");
+    build::build_sysroot(
+        env,
+        args.config_info.sysroot_panic_abort,
+        args.config_info.sysroot_release_channel,
+        &args.config_info,
+    )?;
+    Ok(())
+}
+
+// TODO(GuillaumeGomez): when rewriting in Rust, refactor with the code in tests/lang_tests_common.rs if possible.
+fn run_command_in_vm(
+    command: &[&dyn AsRef<OsStr>],
+    env: &Env,
+    args: &TestArg,
+) -> Result<(), String> {
+    if !args.config_info.run_in_vm {
+        run_command_with_env(command, None, Some(env))?;
+        return Ok(());
+    }
+    let vm_parent_dir = match env.get("CG_GCC_VM_DIR") {
+        Some(dir) if !dir.is_empty() => PathBuf::from(dir.clone()),
+        _ => std::env::current_dir().unwrap(),
+    };
+    let vm_dir = "vm";
+    let exe_to_run = command.first().unwrap();
+    let exe = Path::new(&exe_to_run);
+    let exe_filename = exe.file_name().unwrap();
+    let vm_home_dir = vm_parent_dir.join(vm_dir).join("home");
+    let vm_exe_path = vm_home_dir.join(exe_filename);
+    let inside_vm_exe_path = Path::new("/home").join(exe_filename);
+
+    let sudo_command: &[&dyn AsRef<OsStr>] = &[&"sudo", &"cp", &exe, &vm_exe_path];
+    run_command_with_env(sudo_command, None, Some(env))?;
+
+    let mut vm_command: Vec<&dyn AsRef<OsStr>> =
+        vec![&"sudo", &"chroot", &"qemu-m68k-static", &inside_vm_exe_path];
+    vm_command.extend_from_slice(command);
+    run_command_with_env(&vm_command, Some(&vm_parent_dir), Some(env))?;
+    Ok(())
+}
+
+fn std_tests(env: &Env, args: &TestArg) -> Result<(), String> {
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] arbitrary_self_types_pointers_and_wrappers");
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/arbitrary_self_types_pointers_and_wrappers.rs",
+        &"--crate-name",
+        &"arbitrary_self_types_pointers_and_wrappers",
+        &"--crate-type",
+        &"bin",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_env(&command, None, Some(env))?;
+    run_command_in_vm(
+        &[&Path::new(&args.config_info.cargo_target_dir)
+            .join("arbitrary_self_types_pointers_and_wrappers")],
+        env,
+        args,
+    )?;
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] alloc_system");
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/alloc_system.rs",
+        &"--crate-type",
+        &"lib",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    if !args.no_default_features {
+        command.push(&"--cfg feature=\"master\"");
+    }
+    run_command_with_env(&command, None, Some(env))?;
+
+    // FIXME: doesn't work on m68k.
+    if args.config_info.host_triple != args.config_info.target_triple {
+        // FIXME: create a function "display_if_not_quiet" or something along the line.
+        println!("[AOT] alloc_example");
+        let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+        for arg in args.config_info.rustc_command.iter() {
+            command.push(arg);
+        }
+        command.extend_from_slice(&[
+            &"example/alloc_example.rs",
+            &"--crate-type",
+            &"bin",
+            &"--target",
+            &args.config_info.target_triple,
+        ]);
+        run_command_with_env(&command, None, Some(env))?;
+        run_command_in_vm(
+            &[&Path::new(&args.config_info.cargo_target_dir).join("alloc_example")],
+            env,
+            args,
+        )?;
+    }
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] dst_field_align");
+    // FIXME(antoyo): Re-add -Zmir-opt-level=2 once rust-lang/rust#67529 is fixed.
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/dst-field-align.rs",
+        &"--crate-name",
+        &"dst_field_align",
+        &"--crate-type",
+        &"bin",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_env(&command, None, Some(env))?;
+    run_command_in_vm(
+        &[&Path::new(&args.config_info.cargo_target_dir).join("dst_field_align")],
+        env,
+        args,
+    )?;
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] std_example");
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/std_example.rs",
+        &"--crate-type",
+        &"bin",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    if !args.no_default_features {
+        command.push(&"--cfg feature=\"master\"");
+    }
+    run_command_with_env(&command, None, Some(env))?;
+    run_command_in_vm(
+        &[
+            &Path::new(&args.config_info.cargo_target_dir).join("std_example"),
+            &"--target",
+            &args.config_info.target_triple,
+        ],
+        env,
+        args,
+    )?;
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] subslice-patterns-const-eval");
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/subslice-patterns-const-eval.rs",
+        &"--crate-type",
+        &"bin",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_env(&command, None, Some(env))?;
+    run_command_in_vm(
+        &[&Path::new(&args.config_info.cargo_target_dir).join("subslice-patterns-const-eval")],
+        env,
+        args,
+    )?;
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] track-caller-attribute");
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/track-caller-attribute.rs",
+        &"--crate-type",
+        &"bin",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_env(&command, None, Some(env))?;
+    run_command_in_vm(
+        &[&Path::new(&args.config_info.cargo_target_dir).join("track-caller-attribute")],
+        env,
+        args,
+    )?;
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[AOT] mod_bench");
+    let mut command: Vec<&dyn AsRef<OsStr>> = Vec::new();
+    for arg in args.config_info.rustc_command.iter() {
+        command.push(arg);
+    }
+    command.extend_from_slice(&[
+        &"example/mod_bench.rs",
+        &"--crate-type",
+        &"bin",
+        &"--target",
+        &args.config_info.target_triple,
+    ]);
+    run_command_with_env(&command, None, Some(env))?;
+    // FIXME: the compiled binary is not run. Is it normal?
+
+    Ok(())
+}
+
+fn setup_rustc(env: &mut Env, args: &TestArg) -> Result<(), String> {
+    let toolchain = get_toolchain()?;
+
+    let rust_dir = Some(Path::new("rust"));
+    // If the repository was already cloned, command will fail, so doesn't matter.
+    let _ = run_command_with_output_and_env(
+        &[&"git", &"clone", &"https://github.com/rust-lang/rust.git"],
+        None,
+        Some(env),
+    );
+    run_command_with_output_and_env(&[&"git", &"fetch"], rust_dir, Some(env))?;
+    let rustc_commit = match rustc_version_info(env.get("RUSTC").map(|s| s.as_str()))?.commit_hash {
+        Some(commit_hash) => commit_hash,
+        None => return Err("Couldn't retrieve rustc commit hash".to_string()),
+    };
+    run_command_with_output_and_env(&[&"git", &"checkout", &rustc_commit], rust_dir, Some(env))?;
+    env.insert("RUSTFLAGS".to_string(), String::new());
+    let cargo = String::from_utf8(
+        run_command_with_env(&[&"rustup", &"which", &"cargo"], rust_dir, Some(env))?.stdout,
+    )
+    .map_err(|error| format!("Failed to retrieve cargo path: {:?}", error))
+    .and_then(|cargo| {
+        let cargo = cargo.trim().to_owned();
+        if cargo.is_empty() {
+            Err(format!("`cargo` path is empty"))
+        } else {
+            Ok(cargo)
+        }
+    })?;
+    let llvm_filecheck = String::from_utf8(
+        run_command_with_env(
+            &[
+                &"bash",
+                &"-c",
+                &"which FileCheck-10 || \
+          which FileCheck-11 || \
+          which FileCheck-12 || \
+          which FileCheck-13 || \
+          which FileCheck-14",
+            ],
+            rust_dir,
+            Some(env),
+        )?
+        .stdout,
+    )
+    .map_err(|error| format!("Failed to retrieve LLVM FileCheck: {:?}", error))?;
+    std::fs::write(
+        "rust/config.toml",
+        &format!(
+            r#"change-id = 115898
+
+[rust]
+codegen-backends = []
+deny-warnings = false
+verbose-tests = true
+
+[build]
+cargo = "{cargo}"
+local-rebuild = true
+rustc = "{home}/.rustup/toolchains/{toolchain}-{host_triple}/bin/rustc"
+
+[target.x86_64-unknown-linux-gnu]
+llvm-filecheck = "{llvm_filecheck}"
+
+[llvm]
+download-ci-llvm = false
+"#,
+            cargo = cargo.trim(),
+            home = env.get("HOME").unwrap(),
+            toolchain = toolchain,
+            host_triple = args.config_info.host_triple,
+            llvm_filecheck = llvm_filecheck,
+        ),
+    )
+    .map_err(|error| format!("Failed to write into `rust/config.toml`: {:?}", error))?;
+
+    let rustc_commit = match rustc_version_info(env.get("RUSTC").map(|s| s.as_str()))?.commit_hash {
+        Some(commit_hash) => commit_hash,
+        None => return Err("Couldn't retrieve rustc commit hash".to_string()),
+    };
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("commit: {:?}", rustc_commit);
+    let command: &[&dyn AsRef<OsStr>] = &[&"git", &"checkout", &rustc_commit, &"tests"];
+    run_command_with_output_and_env(command, rust_dir, Some(env))?;
+    Ok(())
+}
+
+fn asm_tests(env: &Env, args: &TestArg) -> Result<(), String> {
+    let mut env = env.clone();
+    setup_rustc(&mut env, args)?;
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[TEST] rustc asm test suite");
+
+    env.insert("COMPILETEST_FORCE_STAGE0".to_string(), "1".to_string());
+
+    run_command_with_env(
+        &[
+            &"./x.py",
+            &"test",
+            &"--run",
+            &"always",
+            &"--stage",
+            &"0",
+            &"tests/assembly/asm",
+            &"--rustc-args",
+            &format!(
+                r#"-Zpanic-abort-tests -Csymbol-mangling-version=v0 \
+                -Zcodegen-backend="{pwd}/target/{channel}/librustc_codegen_gcc.{dylib_ext}" \
+                --sysroot "{pwd}/build_sysroot/sysroot" -Cpanic=abort"#,
+                pwd = std::env::current_dir()
+                    .map_err(|error| format!("`current_dir` failed: {:?}", error))?
+                    .display(),
+                channel = args.channel.as_str(),
+                dylib_ext = args.config_info.dylib_ext,
+            )
+            .as_str(),
+        ],
+        Some(Path::new("rust")),
+        Some(&env),
+    )?;
+    Ok(())
+}
+
+fn run_cargo_command(
+    command: &[&dyn AsRef<OsStr>],
+    cwd: Option<&Path>,
+    env: &Env,
+    args: &TestArg,
+) -> Result<(), String> {
+    run_cargo_command_with_callback(command, cwd, env, args, |cargo_command, cwd, env| {
+        run_command_with_output_and_env(&cargo_command, cwd, Some(env))?;
+        Ok(())
+    })
+}
+
+fn run_cargo_command_with_callback<F>(
+    command: &[&dyn AsRef<OsStr>],
+    cwd: Option<&Path>,
+    env: &Env,
+    args: &TestArg,
+    callback: F,
+) -> Result<(), String>
+where
+    F: Fn(&[&dyn AsRef<OsStr>], Option<&Path>, &Env) -> Result<(), String>,
+{
+    let toolchain = get_toolchain()?;
+    let rustc_version = String::from_utf8(
+        run_command_with_env(&[&args.config_info.rustc_command[0], &"-V"], cwd, Some(env))?.stdout,
+    )
+    .map_err(|error| format!("Failed to retrieve rustc version: {:?}", error))?;
+    let rustc_toolchain_version = String::from_utf8(
+        run_command_with_env(
+            &[
+                &args.config_info.rustc_command[0],
+                &format!("+{}", toolchain),
+                &"-V",
+            ],
+            cwd,
+            Some(env),
+        )?
+        .stdout,
+    )
+    .map_err(|error| format!("Failed to retrieve rustc +toolchain version: {:?}", error))?;
+
+    if rustc_version != rustc_toolchain_version {
+        eprintln!(
+            "rustc_codegen_gcc is built for `{}` but the default rustc version is `{}`.",
+            rustc_toolchain_version, rustc_version,
+        );
+        eprintln!("Using `{}`.", rustc_toolchain_version);
+    }
+    let mut cargo_command: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &toolchain];
+    cargo_command.extend_from_slice(&command);
+    callback(&cargo_command, cwd, env)
+}
+
+// FIXME(antoyo): linker gives multiple definitions error on Linux
+// echo "[BUILD] sysroot in release mode"
+// ./build_sysroot/build_sysroot.sh --release
+
+fn test_libcore(env: &Env, args: &TestArg) -> Result<(), String> {
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[TEST] libcore");
+    let path = Path::new("build_sysroot/sysroot_src/library/core/tests");
+    let _ = remove_dir_all(path.join("target"));
+    run_cargo_command(&[&"test"], Some(path), env, args)?;
+    Ok(())
+}
+
+// echo "[BENCH COMPILE] mod_bench"
+//
+// COMPILE_MOD_BENCH_INLINE="$RUSTC example/mod_bench.rs --crate-type bin -Zmir-opt-level=3 -O --crate-name mod_bench_inline"
+// COMPILE_MOD_BENCH_LLVM_0="rustc example/mod_bench.rs --crate-type bin -Copt-level=0 -o $cargo_target_dir/mod_bench_llvm_0 -Cpanic=abort"
+// COMPILE_MOD_BENCH_LLVM_1="rustc example/mod_bench.rs --crate-type bin -Copt-level=1 -o $cargo_target_dir/mod_bench_llvm_1 -Cpanic=abort"
+// COMPILE_MOD_BENCH_LLVM_2="rustc example/mod_bench.rs --crate-type bin -Copt-level=2 -o $cargo_target_dir/mod_bench_llvm_2 -Cpanic=abort"
+// COMPILE_MOD_BENCH_LLVM_3="rustc example/mod_bench.rs --crate-type bin -Copt-level=3 -o $cargo_target_dir/mod_bench_llvm_3 -Cpanic=abort"
+//
+// Use 100 runs, because a single compilations doesn't take more than ~150ms, so it isn't very slow
+// hyperfine --runs ${COMPILE_RUNS:-100} "$COMPILE_MOD_BENCH_INLINE" "$COMPILE_MOD_BENCH_LLVM_0" "$COMPILE_MOD_BENCH_LLVM_1" "$COMPILE_MOD_BENCH_LLVM_2" "$COMPILE_MOD_BENCH_LLVM_3"
+// echo "[BENCH RUN] mod_bench"
+// hyperfine --runs ${RUN_RUNS:-10} $cargo_target_dir/mod_bench{,_inline} $cargo_target_dir/mod_bench_llvm_*
+
+fn extended_rand_tests(env: &Env, args: &TestArg) -> Result<(), String> {
+    if args.no_default_features {
+        return Ok(());
+    }
+    let path = Path::new("rand");
+    run_cargo_command(&[&"clean"], Some(path), env, args)?;
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[TEST] rust-random/rand");
+    run_cargo_command(&[&"test", &"--workspace"], Some(path), env, args)?;
+    Ok(())
+}
+
+fn extended_regex_example_tests(env: &Env, args: &TestArg) -> Result<(), String> {
+    if args.no_default_features {
+        return Ok(());
+    }
+    let path = Path::new("regex");
+    run_cargo_command(&[&"clean"], Some(path), env, args)?;
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[TEST] rust-lang/regex example shootout-regex-dna");
+    let mut env = env.clone();
+    // newer aho_corasick versions throw a deprecation warning
+    env.insert("CG_RUSTFLAGS".to_string(), "--cap-lints warn".to_string());
+    // Make sure `[codegen mono items] start` doesn't poison the diff
+    run_cargo_command(
+        &[&"build", &"--example", &"shootout-regex-dna"],
+        Some(path),
+        &env,
+        args,
+    )?;
+
+    run_cargo_command_with_callback(
+        &[&"run", &"--example", &"shootout-regex-dna"],
+        Some(path),
+        &env,
+        args,
+        |cargo_command, cwd, env| {
+            // FIXME: rewrite this with `child.stdin.write_all()` because
+            // `examples/regexdna-input.txt` is very small.
+            let mut command: Vec<&dyn AsRef<OsStr>> = vec![&"bash", &"-c"];
+            let cargo_args = cargo_command
+                .iter()
+                .map(|s| s.as_ref().to_str().unwrap())
+                .collect::<Vec<_>>();
+            let bash_command = format!(
+                "cat examples/regexdna-input.txt | {} | grep -v 'Spawned thread' > res.txt",
+                cargo_args.join(" "),
+            );
+            command.push(&bash_command);
+            run_command_with_output_and_env(&command, cwd, Some(env))?;
+            run_command_with_output_and_env(
+                &[&"diff", &"-u", &"res.txt", &"examples/regexdna-output.txt"],
+                cwd,
+                Some(env),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    Ok(())
+}
+
+fn extended_regex_tests(env: &Env, args: &TestArg) -> Result<(), String> {
+    if args.no_default_features {
+        return Ok(());
+    }
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[TEST] rust-lang/regex tests");
+    let mut env = env.clone();
+    env.insert("CG_RUSTFLAGS".to_string(), "--cap-lints warn".to_string());
+    run_cargo_command(
+        &[
+            &"test",
+            &"--tests",
+            &"--",
+            &"--exclude-should-panic",
+            &"--test-threads",
+            &"1",
+            &"-Zunstable-options",
+            &"-q",
+        ],
+        Some(Path::new("regex")),
+        &env,
+        args,
+    )?;
+    Ok(())
+}
+
+fn extended_sysroot_tests(env: &Env, args: &TestArg) -> Result<(), String> {
+    // pushd simple-raytracer
+    // echo "[BENCH COMPILE] ebobby/simple-raytracer"
+    // hyperfine --runs "${RUN_RUNS:-10}" --warmup 1 --prepare "cargo clean" \
+    // "RUSTC=rustc RUSTFLAGS='' cargo build" \
+    // "../cargo.sh build"
+
+    // echo "[BENCH RUN] ebobby/simple-raytracer"
+    // cp ./target/debug/main ./raytracer_cg_gcc
+    // hyperfine --runs "${RUN_RUNS:-10}" ./raytracer_cg_llvm ./raytracer_cg_gcc
+    // popd
+    extended_rand_tests(env, args)?;
+    extended_regex_example_tests(env, args)?;
+    extended_regex_tests(env, args)?;
+
+    Ok(())
+}
+
+fn should_remove_ui_test(content: &str) -> bool {
+    for line in content
+        .lines()
+        .map(|line| line.trim())
+        .filter(|line| !line.is_empty())
+    {
+        if [
+            "// error-pattern:",
+            "// build-fail",
+            "// run-fail",
+            "-Cllvm-args",
+            "//~",
+            "// ~",
+        ]
+        .iter()
+        .any(|check| line.contains(check))
+        {
+            return true;
+        }
+    }
+    false
+}
+
+fn should_not_remove_test(file: &str) -> bool {
+    // contains //~ERROR, but shouldn't be removed
+    [
+        "issues/auxiliary/issue-3136-a.rs",
+        "type-alias-impl-trait/auxiliary/cross_crate_ice.rs",
+        "type-alias-impl-trait/auxiliary/cross_crate_ice2.rs",
+        "macros/rfc-2011-nicer-assert-messages/auxiliary/common.rs",
+        "imports/ambiguous-1.rs",
+        "imports/ambiguous-4-extern.rs",
+        "entry-point/auxiliary/bad_main_functions.rs",
+    ]
+    .iter()
+    .any(|to_ignore| file.ends_with(to_ignore))
+}
+
+fn should_remove_test(path: &Path, path_str: &str) -> bool {
+    // Tests generating errors.
+    path.file_name()
+        .and_then(|name| name.to_str())
+        .map(|name| name.contains("thread"))
+        .unwrap_or(false)
+        || [
+            "consts/issue-miri-1910.rs",
+            // Tests generating errors.
+            "consts/issue-94675.rs",
+            // this test is oom-killed in the CI.
+            "mir/mir_heavy/issue-miri-1910.rs",
+        ]
+        .iter()
+        .any(|to_ignore| path_str.ends_with(to_ignore))
+}
+
+fn test_rustc_inner<F>(env: &Env, args: &TestArg, callback: F) -> Result<(), String>
+where
+    F: Fn() -> Result<bool, String>,
+{
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[TEST] rust-lang/rust");
+    walk_dir(
+        "rust/tests/ui",
+        |dir| {
+            let dir_name = dir.file_name().and_then(|name| name.to_str()).unwrap_or("");
+            if [
+                "abi",
+                "extern",
+                "unsized-locals",
+                "proc-macro",
+                "threads-sendsync",
+                "borrowck",
+                "test-attrs",
+            ]
+            .iter()
+            .any(|name| *name == dir_name)
+            {
+                std::fs::remove_dir_all(dir).map_err(|error| {
+                    format!("Failed to remove folder `{}`: {:?}", dir.display(), error)
+                })?;
+            }
+            Ok(())
+        },
+        |_| Ok(()),
+    )?;
+
+    fn dir_handling(dir: &Path) -> Result<(), String> {
+        walk_dir(dir, dir_handling, file_handling)
+    }
+    fn file_handling(file: &Path) -> Result<(), String> {
+        let path_str = file.display().to_string().replace("\\", "/");
+        if should_not_remove_test(&path_str) {
+            return Ok(());
+        } else if should_remove_test(file, &path_str) {
+            return std::fs::remove_file(file)
+                .map_err(|error| format!("Failed to remove `{}`: {:?}", file.display(), error));
+        }
+        let file_content = std::fs::read_to_string(file)
+            .map_err(|error| format!("Failed to read `{}`: {:?}", file.display(), error))?;
+        if should_remove_ui_test(&file_content) {
+            std::fs::remove_file(file)
+                .map_err(|error| format!("Failed to remove `{}`: {:?}", file.display(), error))?;
+        }
+        Ok(())
+    }
+
+    walk_dir("rust/tests/ui", dir_handling, file_handling)?;
+    let file = "rust/tests/ui/consts/const_cmp_type_id.rs";
+    std::fs::remove_file(file)
+        .map_err(|error| format!("Failed to remove `{}`: {:?}", file, error))?;
+    let file = "rust/tests/ui/consts/issue-73976-monomorphic.rs";
+    std::fs::remove_file(file)
+        .map_err(|error| format!("Failed to remove `{}`: {:?}", file, error))?;
+
+    let mut env = env.clone();
+    setup_rustc(&mut env, args)?;
+    if !callback()? {
+        // FIXME: create a function "display_if_not_quiet" or something along the line.
+        println!("Keeping all UI tests");
+    }
+
+    let nb_parts = args.nb_parts.unwrap_or(0);
+    if nb_parts > 0 {
+        let current_part = args.current_part.unwrap_or(0);
+        // FIXME: create a function "display_if_not_quiet" or something along the line.
+        println!(
+            "Splitting ui_test into {} parts (and running part {})",
+            nb_parts, current_part
+        );
+        let out = String::from_utf8(
+            run_command(
+                &[
+                    &"find",
+                    &"tests/ui",
+                    &"-type",
+                    &"f",
+                    &"-name",
+                    &"*.rs",
+                    &"-not",
+                    &"-path",
+                    &"*/auxiliary/*",
+                ],
+                Some(Path::new("rust")),
+            )?
+            .stdout,
+        )
+        .map_err(|error| format!("Failed to retrieve output of find command: {:?}", error))?;
+        let mut files = out
+            .split('\n')
+            .map(|line| line.trim())
+            .filter(|line| !line.is_empty())
+            .collect::<Vec<_>>();
+        // To ensure it'll be always the same sub files, we sort the content.
+        files.sort();
+        // We increment the number of tests by one because if this is an odd number, we would skip
+        // one test.
+        let count = files.len() / nb_parts + 1;
+        let start = nb_parts * count;
+        let end = start + count;
+        for (pos, path) in files.iter().enumerate() {
+            if pos >= start && pos <= end {
+                continue;
+            }
+            std::fs::remove_file(path)
+                .map_err(|error| format!("Failed to remove `{}`: {:?}", path, error))?;
+        }
+    }
+
+    // FIXME: create a function "display_if_not_quiet" or something along the line.
+    println!("[TEST] rustc test suite");
+    env.insert("COMPILETEST_FORCE_STAGE0".to_string(), "1".to_string());
+    let rustc_args = env
+        .get("RUSTFLAGS")
+        .expect("RUSTFLAGS should not be empty at this stage");
+    run_command_with_output_and_env(
+        &[
+            &"./x.py",
+            &"test",
+            &"--run",
+            &"always",
+            &"--stage",
+            &"0",
+            &"tests/ui",
+            &"--rustc-args",
+            &rustc_args,
+        ],
+        Some(Path::new("rust")),
+        Some(&env),
+    )?;
+    Ok(())
+}
+
+fn test_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
+    test_rustc_inner(env, args, || Ok(false))
+}
+
+fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
+    test_rustc_inner(env, args, || {
+        // Removing all tests.
+        run_command(
+            &[
+                &"find",
+                &"tests/ui",
+                &"-type",
+                &"f",
+                &"-name",
+                &"*.rs",
+                &"-not",
+                &"-path",
+                &"*/auxiliary/*",
+                &"-delete",
+            ],
+            Some(Path::new("rust")),
+        )?;
+        // Putting back only the failing ones.
+        run_command(
+            &[
+                &"xargs",
+                &"-a",
+                &"../failing-ui-tests.txt",
+                &"-d'\n'",
+                &"git",
+                &"checkout",
+                &"--",
+            ],
+            Some(Path::new("rust")),
+        )?;
+        Ok(true)
+    })
+}
+
+fn test_successful_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
+    test_rustc_inner(env, args, || {
+        // Removing the failing tests.
+        run_command(
+            &[
+                &"xargs",
+                &"-a",
+                &"../failing-ui-tests.txt",
+                &"-d'\n'",
+                &"rm",
+            ],
+            Some(Path::new("rust")),
+        )?;
+        Ok(true)
+    })
+}
+
+fn clean_ui_tests(_env: &Env, _args: &TestArg) -> Result<(), String> {
+    run_command(
+        &[
+            &"find",
+            &"rust/build/x86_64-unknown-linux-gnu/test/ui/",
+            &"-name",
+            &"stamp",
+            &"-delete",
+        ],
+        None,
+    )?;
+    Ok(())
+}
+
+fn run_all(env: &Env, args: &TestArg) -> Result<(), String> {
+    clean(env, args)?;
+    mini_tests(env, args)?;
+    build_sysroot(env, args)?;
+    std_tests(env, args)?;
+    // asm_tests(env, args)?;
+    test_libcore(env, args)?;
+    extended_sysroot_tests(env, args)?;
+    test_rustc(env, args)?;
+    Ok(())
 }
 
 pub fn run() -> Result<(), String> {
-    let mut args: Vec<&dyn AsRef<std::ffi::OsStr>> = vec![&"bash", &"test.sh"];
-    let extra_args = std::env::args().skip(2).collect::<Vec<_>>();
-    get_args(&mut args, &extra_args);
-    let current_dir = std::env::current_dir().map_err(|error| format!("`current_dir` failed: {:?}", error))?;
-    run_command_with_output(args.as_slice(), Some(&current_dir))
+    let mut args = match TestArg::new()? {
+        Some(args) => args,
+        None => return Ok(()),
+    };
+    let mut env: HashMap<String, String> = std::env::vars().collect();
+
+    env.insert("LD_LIBRARY_PATH".to_string(), args.gcc_path.clone());
+    env.insert("LIBRARY_PATH".to_string(), args.gcc_path.clone());
+
+    build_if_no_backend(&env, &args)?;
+    if args.build_only {
+        println!("Since it's build only, exiting...");
+        return Ok(());
+    }
+
+    let test_flags = split_args(env.get("TEST_FLAGS").unwrap_or(&String::new()));
+    args.config_info
+        .setup(&mut env, &test_flags, Some(&args.gcc_path))?;
+
+    if args.runners.is_empty() {
+        run_all(&env, &args)?;
+    } else {
+        let runners = get_runners();
+        for runner in args.runners.iter() {
+            runners.get(runner.as_str()).unwrap().1(&env, &args)?;
+        }
+    }
+
+    Ok(())
 }
diff --git a/build_system/src/utils.rs b/build_system/src/utils.rs
index 536f33a8029..ba1e040cb20 100644
--- a/build_system/src/utils.rs
+++ b/build_system/src/utils.rs
@@ -143,17 +143,56 @@ pub fn get_os_name() -> Result<String, String> {
     }
 }
 
-pub fn get_rustc_host_triple() -> Result<String, String> {
-    let output = run_command(&[&"rustc", &"-vV"], None)?;
+#[derive(Default)]
+pub struct RustcVersionInfo {
+    pub version: String,
+    pub host: Option<String>,
+    pub commit_hash: Option<String>,
+    pub commit_date: Option<String>,
+}
+
+pub fn rustc_version_info(rustc: Option<&str>) -> Result<RustcVersionInfo, String> {
+    let output = run_command(&[&rustc.unwrap_or("rustc"), &"-vV"], None)?;
     let content = std::str::from_utf8(&output.stdout).unwrap_or("");
 
+    let mut info = RustcVersionInfo::default();
+
     for line in content.split('\n').map(|line| line.trim()) {
-        if !line.starts_with("host:") {
-            continue;
+        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()),
+            _ => {}
         }
-        return Ok(line.split(':').nth(1).unwrap().trim().to_string());
     }
-    Err("Cannot find host triple".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(toolchain) => Ok(toolchain.to_string()),
+        None => Err("Couldn't find `channel` in `rust-toolchain` file".to_string()),
+    }
 }
 
 pub fn get_gcc_path() -> Result<String, String> {
@@ -238,3 +277,56 @@ where
     }
     Ok(())
 }
+
+pub fn split_args(args: &str) -> Vec<String> {
+    let mut out = Vec::new();
+    let mut start = 0;
+    let mut iter = args.char_indices().peekable();
+
+    while iter.peek().is_some() {
+        while let Some((pos, c)) = iter.next() {
+            if c == ' ' {
+                if pos != 0 {
+                    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 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 {
+                    out.push(args[start..].to_string());
+                    return out;
+                }
+            } else if c == '\\' {
+                // We skip the escaped character.
+                iter.next();
+            }
+        }
+    }
+    let s = args[start..].trim();
+    if !s.is_empty() {
+        out.push(s.to_string());
+    }
+    out
+}