Fix rustc wrapper to read command files for linking detection (#4481)
* Fix rustc wrapper to read command files for linking detection * fix formatting for cargo fmt check
This commit is contained in:
1 parent
d8d11db403
commit
5591070e92
1 file changed
+63
-25
@@ -14,7 +14,7 @@ pub const DX_RUSTC_WRAPPER_ENV_VAR: &str = "DX_RUSTC";
|
|||||||
/// This is primarily used to intercept cargo, enabling fast hot-patching by caching the environment
|
/// This is primarily used to intercept cargo, enabling fast hot-patching by caching the environment
|
||||||
/// cargo setups up for the user's current project.
|
/// cargo setups up for the user's current project.
|
||||||
///
|
///
|
||||||
/// In a differenet world we could simply rely on cargo printing link args and the rustc command, but
|
/// In a different world we could simply rely on cargo printing link args and the rustc command, but
|
||||||
/// it doesn't seem to output that in a reliable, parseable, cross-platform format (ie using command
|
/// it doesn't seem to output that in a reliable, parseable, cross-platform format (ie using command
|
||||||
/// files on windows...), so we're forced to do this interception nonsense.
|
/// files on windows...), so we're forced to do this interception nonsense.
|
||||||
pub fn is_wrapping_rustc() -> bool {
|
pub fn is_wrapping_rustc() -> bool {
|
||||||
@@ -25,19 +25,49 @@ pub fn is_wrapping_rustc() -> bool {
|
|||||||
pub struct RustcArgs {
|
pub struct RustcArgs {
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub envs: Vec<(String, String)>,
|
pub envs: Vec<(String, String)>,
|
||||||
pub link_args: Vec<String>,
|
pub link_args: Vec<String>, // I don't believe this is used anymore
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the arguments indicate a linking step, including those in command files.
|
||||||
|
fn has_linking_args() -> bool {
|
||||||
|
for arg in std::env::args() {
|
||||||
|
// Direct check for linker-like arguments
|
||||||
|
if arg.ends_with(".o") || arg == "-flavor" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check inside command files
|
||||||
|
if let Some(path_str) = arg.strip_prefix('@') {
|
||||||
|
if let Ok(file_binary) = std::fs::read(path_str) {
|
||||||
|
// Handle both UTF-8 and UTF-16LE encodings for response files.
|
||||||
|
let content = String::from_utf8(file_binary.clone()).unwrap_or_else(|_| {
|
||||||
|
let binary_u16le: Vec<u16> = file_binary
|
||||||
|
.chunks_exact(2)
|
||||||
|
.map(|a| u16::from_le_bytes([a[0], a[1]]))
|
||||||
|
.collect();
|
||||||
|
String::from_utf16_lossy(&binary_u16le)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if any line in the command file contains linking indicators.
|
||||||
|
if content.lines().any(|line| {
|
||||||
|
let trimmed_line = line.trim().trim_matches('"');
|
||||||
|
trimmed_line.ends_with(".o") || trimmed_line == "-flavor"
|
||||||
|
}) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run rustc directly, but output the result to a file.
|
/// Run rustc directly, but output the result to a file.
|
||||||
///
|
///
|
||||||
/// <https://doc.rust-lang.org/cargo/reference/config.html#buildrustc>
|
/// <https://doc.rust-lang.org/cargo/reference/config.html#buildrustc>
|
||||||
pub async fn run_rustc() {
|
pub async fn run_rustc() {
|
||||||
// if we happen to be both a rustc wrapper and a linker, we want to run the linker if the arguments seem linker-y
|
// If we are being asked to link, delegate to the linker action.
|
||||||
// this is a stupid hack
|
if has_linking_args() {
|
||||||
if std::env::args()
|
|
||||||
.take(5)
|
|
||||||
.any(|arg| arg.ends_with(".o") || arg == "-flavor" || arg.starts_with("@"))
|
|
||||||
{
|
|
||||||
return crate::link::LinkAction::from_env()
|
return crate::link::LinkAction::from_env()
|
||||||
.expect("Linker action not found")
|
.expect("Linker action not found")
|
||||||
.run_link()
|
.run_link()
|
||||||
@@ -45,11 +75,15 @@ pub async fn run_rustc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let var_file: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR)
|
let var_file: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR)
|
||||||
.expect("DX_RUSTC not set")
|
.expect("DX_RUSTC env var must be set")
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
// Cargo invokes a wrapper like: `wrapper-name rustc [args...]`
|
||||||
|
// We skip our own executable name (`wrapper-name`) to get the args passed to us.
|
||||||
|
let captured_args = args().skip(1).collect::<Vec<_>>();
|
||||||
|
|
||||||
let rustc_args = RustcArgs {
|
let rustc_args = RustcArgs {
|
||||||
args: args().skip(1).collect::<Vec<_>>(),
|
args: captured_args.clone(),
|
||||||
envs: vars().collect::<_>(),
|
envs: vars().collect::<_>(),
|
||||||
link_args: Default::default(),
|
link_args: Default::default(),
|
||||||
};
|
};
|
||||||
@@ -63,27 +97,31 @@ pub async fn run_rustc() {
|
|||||||
.nth(1)
|
.nth(1)
|
||||||
.is_some_and(|name| name != "___")
|
.is_some_and(|name| name != "___")
|
||||||
{
|
{
|
||||||
std::fs::create_dir_all(var_file.parent().expect("Failed to get parent dir"))
|
let parent_dir = var_file
|
||||||
.expect("Failed to create parent dir");
|
.parent()
|
||||||
std::fs::write(
|
.expect("Args file path has no parent directory");
|
||||||
&var_file,
|
std::fs::create_dir_all(parent_dir)
|
||||||
serde_json::to_string(&rustc_args).expect("Failed to serialize rustc args"),
|
.expect("Failed to create parent directory for args file");
|
||||||
)
|
|
||||||
.expect("Failed to write rustc args to file");
|
let serialized_args =
|
||||||
|
serde_json::to_string(&rustc_args).expect("Failed to serialize rustc args");
|
||||||
|
|
||||||
|
std::fs::write(&var_file, serialized_args).expect("Failed to write rustc args to file");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the actual rustc command
|
// Run the actual rustc command.
|
||||||
// We want all stdout/stderr to be inherited, so the running process can see the output
|
// We want all stdout/stderr to be inherited, so the user sees the compiler output.
|
||||||
//
|
|
||||||
// Note that the args format we get from the wrapper includes the `rustc` command itself, so we
|
|
||||||
// need to skip that - we already skipped the first arg when we created the args struct.
|
|
||||||
let mut cmd = std::process::Command::new("rustc");
|
let mut cmd = std::process::Command::new("rustc");
|
||||||
cmd.args(rustc_args.args.iter().skip(1));
|
|
||||||
|
// The first argument in `captured_args` is "rustc", which we need to skip
|
||||||
|
// when passing arguments to the `rustc` command we are spawning.
|
||||||
|
cmd.args(captured_args.iter().skip(1));
|
||||||
cmd.envs(rustc_args.envs);
|
cmd.envs(rustc_args.envs);
|
||||||
cmd.stdout(std::process::Stdio::inherit());
|
cmd.stdout(std::process::Stdio::inherit());
|
||||||
cmd.stderr(std::process::Stdio::inherit());
|
cmd.stderr(std::process::Stdio::inherit());
|
||||||
cmd.current_dir(std::env::current_dir().expect("Failed to get current dir"));
|
cmd.current_dir(std::env::current_dir().expect("Failed to get current dir"));
|
||||||
|
|
||||||
// Propagate the exit code
|
// Spawn the process and propagate its exit code.
|
||||||
std::process::exit(cmd.status().unwrap().code().unwrap())
|
let status = cmd.status().expect("Failed to execute rustc command");
|
||||||
|
std::process::exit(status.code().unwrap_or(1)); // Exit with 1 if process was killed by signal
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user