diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index bd2f65925bb..689fdc5dfeb 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -3926,11 +3926,17 @@ impl<'test> TestCx<'test> {
             cmd.env("IS_MSVC", "1")
                 .env("IS_WINDOWS", "1")
                 .env("MSVC_LIB", format!("'{}' -nologo", lib.display()))
-                .env("CC", format!("'{}' {}", self.config.cc, cflags))
-                .env("CXX", format!("'{}' {}", &self.config.cxx, cxxflags));
+                // Note: we diverge from legacy run_make and don't lump `CC` the compiler and
+                // default flags together.
+                .env("CC_DEFAULT_FLAGS", &cflags)
+                .env("CC", &self.config.cc)
+                .env("CXX_DEFAULT_FLAGS", &cxxflags)
+                .env("CXX", &self.config.cxx);
         } else {
-            cmd.env("CC", format!("{} {}", self.config.cc, self.config.cflags))
-                .env("CXX", format!("{} {}", self.config.cxx, self.config.cxxflags))
+            cmd.env("CC_DEFAULT_FLAGS", &self.config.cflags)
+                .env("CC", &self.config.cc)
+                .env("CXX_DEFAULT_FLAGS", &self.config.cxxflags)
+                .env("CXX", &self.config.cxx)
                 .env("AR", &self.config.ar);
 
             if self.config.target.contains("windows") {
diff --git a/src/tools/run-make-support/src/cc.rs b/src/tools/run-make-support/src/cc.rs
new file mode 100644
index 00000000000..2c9ad4f1700
--- /dev/null
+++ b/src/tools/run-make-support/src/cc.rs
@@ -0,0 +1,202 @@
+use std::env;
+use std::path::Path;
+use std::process::{Command, Output};
+
+use crate::{bin_name, cygpath_windows, handle_failed_output, is_msvc, is_windows, tmp_dir, uname};
+
+/// Construct a new platform-specific C compiler invocation.
+///
+/// WARNING: This means that what flags are accepted by the underlying C compiler is
+/// platform- AND compiler-specific. Consult the relevant docs for `gcc`, `clang` and `mvsc`.
+pub fn cc() -> Cc {
+    Cc::new()
+}
+
+/// A platform-specific C compiler invocation builder. The specific C compiler used is
+/// passed down from compiletest.
+#[derive(Debug)]
+pub struct Cc {
+    cmd: Command,
+}
+
+impl Cc {
+    /// Construct a new platform-specific C compiler invocation.
+    ///
+    /// WARNING: This means that what flags are accepted by the underlying C compile is
+    /// platform- AND compiler-specific. Consult the relevant docs for `gcc`, `clang` and `mvsc`.
+    pub fn new() -> Self {
+        let compiler = env::var("CC").unwrap();
+
+        let mut cmd = Command::new(compiler);
+
+        let default_cflags = env::var("CC_DEFAULT_FLAGS").unwrap();
+        for flag in default_cflags.split(char::is_whitespace) {
+            cmd.arg(flag);
+        }
+
+        Self { cmd }
+    }
+
+    /// Specify path of the input file.
+    pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(path.as_ref());
+        self
+    }
+
+    /// Add a *platform-and-compiler-specific* argument. Please consult the docs for the various
+    /// possible C compilers on the various platforms to check which arguments are legal for
+    /// which compiler.
+    pub fn arg(&mut self, flag: &str) -> &mut Self {
+        self.cmd.arg(flag);
+        self
+    }
+
+    /// Add multiple *platform-and-compiler-specific* arguments. Please consult the docs for the
+    /// various possible C compilers on the various platforms to check which arguments are legal
+    /// for which compiler.
+    pub fn args(&mut self, args: &[&str]) -> &mut Self {
+        self.cmd.args(args);
+        self
+    }
+
+    /// Specify `-o` or `-Fe`/`-Fo` depending on platform/compiler. This assumes that the executable
+    /// is under `$TMPDIR`.
+    pub fn out_exe(&mut self, name: &str) -> &mut Self {
+        // Ref: tools.mk (irrelevant lines omitted):
+        //
+        // ```makefile
+        // ifdef IS_MSVC
+        //     OUT_EXE=-Fe:`cygpath -w $(TMPDIR)/$(call BIN,$(1))` \
+        //         -Fo:`cygpath -w $(TMPDIR)/$(1).obj`
+        // else
+        //     OUT_EXE=-o $(TMPDIR)/$(1)
+        // endif
+        // ```
+
+        if is_msvc() {
+            let fe_path = cygpath_windows(tmp_dir().join(bin_name(name)));
+            let fo_path = cygpath_windows(tmp_dir().join(format!("{name}.obj")));
+            self.cmd.arg(format!("-Fe:{fe_path}"));
+            self.cmd.arg(format!("-Fo:{fo_path}"));
+        } else {
+            self.cmd.arg("-o");
+            self.cmd.arg(tmp_dir().join(name));
+        }
+
+        self
+    }
+
+    /// Run the constructed C invocation command and assert that it is successfully run.
+    #[track_caller]
+    pub fn run(&mut self) -> Output {
+        let caller_location = std::panic::Location::caller();
+        let caller_line_number = caller_location.line();
+
+        let output = self.cmd.output().unwrap();
+        if !output.status.success() {
+            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
+        }
+        output
+    }
+
+    /// Inspect what the underlying [`Command`] is up to the current construction.
+    pub fn inspect(&mut self, f: impl FnOnce(&Command)) -> &mut Self {
+        f(&self.cmd);
+        self
+    }
+}
+
+/// `EXTRACFLAGS`
+pub fn extra_c_flags() -> Vec<&'static str> {
+    // Adapted from tools.mk (trimmed):
+    //
+    // ```makefile
+    // ifdef IS_WINDOWS
+    //     ifdef IS_MSVC
+    //         EXTRACFLAGS := ws2_32.lib userenv.lib advapi32.lib bcrypt.lib ntdll.lib synchronization.lib
+    //     else
+    //         EXTRACFLAGS := -lws2_32 -luserenv -lbcrypt -lntdll -lsynchronization
+    //     endif
+    // else
+    //     ifeq ($(UNAME),Darwin)
+    //         EXTRACFLAGS := -lresolv
+    //     else
+    //         ifeq ($(UNAME),FreeBSD)
+    //             EXTRACFLAGS := -lm -lpthread -lgcc_s
+    //         else
+    //             ifeq ($(UNAME),SunOS)
+    //                 EXTRACFLAGS := -lm -lpthread -lposix4 -lsocket -lresolv
+    //             else
+    //                 ifeq ($(UNAME),OpenBSD)
+    //                     EXTRACFLAGS := -lm -lpthread -lc++abi
+    //                 else
+    //                     EXTRACFLAGS := -lm -lrt -ldl -lpthread
+    //                 endif
+    //             endif
+    //         endif
+    //     endif
+    // endif
+    // ```
+
+    if is_windows() {
+        if is_msvc() {
+            vec![
+                "ws2_32.lib",
+                "userenv.lib",
+                "advapi32.lib",
+                "bcrypt.lib",
+                "ntdll.lib",
+                "synchronization.lib",
+            ]
+        } else {
+            vec!["-lws2_32", "-luserenv", "-lbcrypt", "-lntdll", "-lsynchronization"]
+        }
+    } else {
+        match uname() {
+            n if n.contains("Darwin") => vec!["-lresolv"],
+            n if n.contains("FreeBSD") => vec!["-lm", "-lpthread", "-lgcc_s"],
+            n if n.contains("SunOS") => {
+                vec!["-lm", "-lpthread", "-lposix4", "-lsocket", "-lresolv"]
+            }
+            n if n.contains("OpenBSD") => vec!["-lm", "-lpthread", "-lc++abi"],
+            _ => vec!["-lm", "-lrt", "-ldl", "-lpthread"],
+        }
+    }
+}
+
+/// `EXTRACXXFLAGS`
+pub fn extra_cxx_flags() -> Vec<&'static str> {
+    // Adapted from tools.mk (trimmed):
+    //
+    // ```makefile
+    // ifdef IS_WINDOWS
+    //     ifdef IS_MSVC
+    //     else
+    //         EXTRACXXFLAGS := -lstdc++
+    //     endif
+    // else
+    //     ifeq ($(UNAME),Darwin)
+    //         EXTRACXXFLAGS := -lc++
+    //     else
+    //         ifeq ($(UNAME),FreeBSD)
+    //         else
+    //             ifeq ($(UNAME),SunOS)
+    //             else
+    //                 ifeq ($(UNAME),OpenBSD)
+    //                 else
+    //                     EXTRACXXFLAGS := -lstdc++
+    //                 endif
+    //             endif
+    //         endif
+    //     endif
+    // endif
+    // ```
+    if is_windows() {
+        if is_msvc() { vec![] } else { vec!["-lstdc++"] }
+    } else {
+        match uname() {
+            n if n.contains("Darwin") => vec!["-lc++"],
+            _ => vec!["-lstdc++"],
+        }
+    }
+}
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 48fa2bbf1ac..e70acf58571 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -1,14 +1,16 @@
+pub mod cc;
 pub mod run;
 pub mod rustc;
 pub mod rustdoc;
 
 use std::env;
-use std::path::PathBuf;
-use std::process::Output;
+use std::path::{Path, PathBuf};
+use std::process::{Command, Output};
 
 pub use object;
 pub use wasmparser;
 
+pub use cc::{cc, extra_c_flags, extra_cxx_flags, Cc};
 pub use run::{run, run_fail};
 pub use rustc::{aux_build, rustc, Rustc};
 pub use rustdoc::{bare_rustdoc, rustdoc, Rustdoc};
@@ -18,6 +20,89 @@ pub fn tmp_dir() -> PathBuf {
     env::var_os("TMPDIR").unwrap().into()
 }
 
+/// `TARGET`
+pub fn target() -> String {
+    env::var("TARGET").unwrap()
+}
+
+/// Check if target is windows-like.
+pub fn is_windows() -> bool {
+    env::var_os("IS_WINDOWS").is_some()
+}
+
+/// Check if target uses msvc.
+pub fn is_msvc() -> bool {
+    env::var_os("IS_MSVC").is_some()
+}
+
+/// Construct a path to a static library under `$TMPDIR` given the library name. This will return a
+/// path with `$TMPDIR` joined with platform-and-compiler-specific library name.
+pub fn static_lib(name: &str) -> PathBuf {
+    tmp_dir().join(static_lib_name(name))
+}
+
+/// Construct the static library name based on the platform.
+pub fn static_lib_name(name: &str) -> String {
+    // See tools.mk (irrelevant lines omitted):
+    //
+    // ```makefile
+    // ifeq ($(UNAME),Darwin)
+    //     STATICLIB = $(TMPDIR)/lib$(1).a
+    // else
+    //     ifdef IS_WINDOWS
+    //         ifdef IS_MSVC
+    //             STATICLIB = $(TMPDIR)/$(1).lib
+    //         else
+    //             STATICLIB = $(TMPDIR)/lib$(1).a
+    //         endif
+    //     else
+    //         STATICLIB = $(TMPDIR)/lib$(1).a
+    //     endif
+    // endif
+    // ```
+    assert!(!name.contains(char::is_whitespace), "name cannot contain whitespace");
+
+    if target().contains("msvc") { format!("{name}.lib") } else { format!("lib{name}.a") }
+}
+
+/// Construct the binary name based on platform.
+pub fn bin_name(name: &str) -> String {
+    if is_windows() { format!("{name}.exe") } else { name.to_string() }
+}
+
+/// Use `cygpath -w` on a path to get a Windows path string back. This assumes that `cygpath` is
+/// available on the platform!
+#[track_caller]
+pub fn cygpath_windows<P: AsRef<Path>>(path: P) -> String {
+    let caller_location = std::panic::Location::caller();
+    let caller_line_number = caller_location.line();
+
+    let mut cygpath = Command::new("cygpath");
+    cygpath.arg("-w");
+    cygpath.arg(path.as_ref());
+    let output = cygpath.output().unwrap();
+    if !output.status.success() {
+        handle_failed_output(&format!("{:#?}", cygpath), output, caller_line_number);
+    }
+    let s = String::from_utf8(output.stdout).unwrap();
+    // cygpath -w can attach a newline
+    s.trim().to_string()
+}
+
+/// Run `uname`. This assumes that `uname` is available on the platform!
+#[track_caller]
+pub fn uname() -> String {
+    let caller_location = std::panic::Location::caller();
+    let caller_line_number = caller_location.line();
+
+    let mut uname = Command::new("uname");
+    let output = uname.output().unwrap();
+    if !output.status.success() {
+        handle_failed_output(&format!("{:#?}", uname), output, caller_line_number);
+    }
+    String::from_utf8(output.stdout).unwrap()
+}
+
 fn handle_failed_output(cmd: &str, output: Output, caller_line_number: u32) -> ! {
     if output.status.success() {
         eprintln!("command incorrectly succeeded at line {caller_line_number}");
diff --git a/src/tools/run-make-support/src/run.rs b/src/tools/run-make-support/src/run.rs
index 42dcf54da22..e33ea9d6e40 100644
--- a/src/tools/run-make-support/src/run.rs
+++ b/src/tools/run-make-support/src/run.rs
@@ -2,17 +2,14 @@ use std::env;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Output};
 
-use super::handle_failed_output;
+use crate::is_windows;
 
-fn run_common(bin_name: &str) -> (Command, Output) {
-    let target = env::var("TARGET").unwrap();
-
-    let bin_name =
-        if target.contains("windows") { format!("{}.exe", bin_name) } else { bin_name.to_owned() };
+use super::{bin_name, handle_failed_output};
 
+fn run_common(name: &str) -> (Command, Output) {
     let mut bin_path = PathBuf::new();
     bin_path.push(env::var("TMPDIR").unwrap());
-    bin_path.push(&bin_name);
+    bin_path.push(&bin_name(name));
     let ld_lib_path_envvar = env::var("LD_LIB_PATH_ENVVAR").unwrap();
     let mut cmd = Command::new(bin_path);
     cmd.env(&ld_lib_path_envvar, {
@@ -27,7 +24,7 @@ fn run_common(bin_name: &str) -> (Command, Output) {
         env::join_paths(paths.iter()).unwrap()
     });
 
-    if target.contains("windows") {
+    if is_windows() {
         let mut paths = vec![];
         for p in env::split_paths(&std::env::var("PATH").unwrap_or(String::new())) {
             paths.push(p.to_path_buf());
@@ -42,11 +39,11 @@ fn run_common(bin_name: &str) -> (Command, Output) {
 
 /// Run a built binary and make sure it succeeds.
 #[track_caller]
-pub fn run(bin_name: &str) -> Output {
+pub fn run(name: &str) -> Output {
     let caller_location = std::panic::Location::caller();
     let caller_line_number = caller_location.line();
 
-    let (cmd, output) = run_common(bin_name);
+    let (cmd, output) = run_common(name);
     if !output.status.success() {
         handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
     }
@@ -55,11 +52,11 @@ pub fn run(bin_name: &str) -> Output {
 
 /// Run a built binary and make sure it fails.
 #[track_caller]
-pub fn run_fail(bin_name: &str) -> Output {
+pub fn run_fail(name: &str) -> Output {
     let caller_location = std::panic::Location::caller();
     let caller_line_number = caller_location.line();
 
-    let (cmd, output) = run_common(bin_name);
+    let (cmd, output) = run_common(name);
     if output.status.success() {
         handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
     }
diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt
index 347ea1223eb..26a774d15f0 100644
--- a/src/tools/tidy/src/allowed_run_make_makefiles.txt
+++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt
@@ -5,7 +5,6 @@ run-make/allocator-shim-circular-deps/Makefile
 run-make/allow-non-lint-warnings-cmdline/Makefile
 run-make/allow-warnings-cmdline-stability/Makefile
 run-make/archive-duplicate-names/Makefile
-run-make/arguments-non-c-like-enum/Makefile
 run-make/atomic-lock-free/Makefile
 run-make/bare-outfile/Makefile
 run-make/branch-protection-check-IBT/Makefile
diff --git a/tests/run-make/arguments-non-c-like-enum/Makefile b/tests/run-make/arguments-non-c-like-enum/Makefile
deleted file mode 100644
index 0c8d8bf3acc..00000000000
--- a/tests/run-make/arguments-non-c-like-enum/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-# ignore-cross-compile
-include ../tools.mk
-
-all:
-	$(RUSTC) --crate-type=staticlib nonclike.rs
-	$(CC) test.c $(call STATICLIB,nonclike) $(call OUT_EXE,test) \
-		$(EXTRACFLAGS) $(EXTRACXXFLAGS)
-	$(call RUN,test)
diff --git a/tests/run-make/arguments-non-c-like-enum/rmake.rs b/tests/run-make/arguments-non-c-like-enum/rmake.rs
new file mode 100644
index 00000000000..624a7fb2251
--- /dev/null
+++ b/tests/run-make/arguments-non-c-like-enum/rmake.rs
@@ -0,0 +1,20 @@
+//! Check that non-trivial `repr(C)` enum in Rust has valid C layout.
+//@ ignore-cross-compile
+
+extern crate run_make_support;
+
+use run_make_support::{cc, extra_c_flags, extra_cxx_flags, run, rustc, static_lib};
+
+pub fn main() {
+    use std::path::Path;
+
+    rustc().input("nonclike.rs").crate_type("staticlib").run();
+    cc().input("test.c")
+        .input(static_lib("nonclike"))
+        .out_exe("test")
+        .args(&extra_c_flags())
+        .args(&extra_cxx_flags())
+        .inspect(|cmd| eprintln!("{cmd:?}"))
+        .run();
+    run("test");
+}