From 5b1516bc91187848b81070ce145a93014a1269ef Mon Sep 17 00:00:00 2001 From: Jamey Sharp Date: Fri, 9 May 2025 06:20:36 -0700 Subject: [PATCH] Allow running multiple transpiles in parallel If c2rust-transpile needs to generate a temporary compile_commands.json, previously it used a fixed path for it across all invocations. Instead, create a uniquely-named temporary directory for it, so that parallel transpiles don't clobber each other. I've used a two-year-old release of tempfile to avoid adding a bunch of duplicate versions of other crates to Cargo.lock, but presumably some upgrades ought to happen sooner or later. --- Cargo.lock | 1 + c2rust-transpile/Cargo.toml | 1 + c2rust-transpile/src/lib.rs | 18 ++++++++++++---- c2rust/src/bin/c2rust-transpile.rs | 33 +++++++++++++++--------------- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bea57aa30..695d6d4d15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,7 @@ dependencies = [ "strum", "strum_macros", "syn 1.0.109", + "tempfile", ] [[package]] diff --git a/c2rust-transpile/Cargo.toml b/c2rust-transpile/Cargo.toml index 31d784e754..3ead86f69f 100644 --- a/c2rust-transpile/Cargo.toml +++ b/c2rust-transpile/Cargo.toml @@ -37,6 +37,7 @@ smallvec = "1.0" strum = "0.24" strum_macros = "0.24" syn = { version = "1.0", features = ["full", "extra-traits", "parsing", "printing"]} +tempfile = "3.5.0" [features] # Force static linking of LLVM diff --git a/c2rust-transpile/src/lib.rs b/c2rust-transpile/src/lib.rs index 60525b8f5e..dccd1d9623 100644 --- a/c2rust-transpile/src/lib.rs +++ b/c2rust-transpile/src/lib.rs @@ -25,6 +25,7 @@ use itertools::Itertools; use log::{info, warn}; use regex::Regex; use serde_derive::Serialize; +pub use tempfile::TempDir; use crate::c_ast::Printer; use crate::c_ast::*; @@ -226,8 +227,18 @@ fn get_module_name( file.to_str().map(String::from) } -pub fn create_temp_compile_commands(sources: &[PathBuf]) -> PathBuf { - let temp_path = std::env::temp_dir().join("compile_commands.json"); +pub fn create_temp_compile_commands(sources: &[PathBuf]) -> (TempDir, PathBuf) { + // If we generate the same path here on every run, then we can't run + // multiple transpiles in parallel, so we need a unique path. But clang + // won't read this file unless it is named exactly "compile_commands.json", + // so we can't change the filename. Instead, create a temporary directory + // with a unique name, and put the file there. + let temp_dir = tempfile::Builder::new() + .prefix("c2rust-") + .tempdir() + .expect("Failed to create temporary directory for compile_commands.json"); + let temp_path = temp_dir.path().join("compile_commands.json"); + let compile_commands: Vec = sources .iter() .map(|source_file| { @@ -252,8 +263,7 @@ pub fn create_temp_compile_commands(sources: &[PathBuf]) -> PathBuf { File::create(&temp_path).expect("Failed to create temporary compile_commands.json"); file.write_all(json_content.as_bytes()) .expect("Failed to write to temporary compile_commands.json"); - - temp_path + (temp_dir, temp_path) } /// Main entry point to transpiler. Called from CLI tools with the result of diff --git a/c2rust/src/bin/c2rust-transpile.rs b/c2rust/src/bin/c2rust-transpile.rs index 165b1edcda..8d485579c0 100644 --- a/c2rust/src/bin/c2rust-transpile.rs +++ b/c2rust/src/bin/c2rust-transpile.rs @@ -1,7 +1,7 @@ use clap::{Parser, ValueEnum}; use log::LevelFilter; use regex::Regex; -use std::{fs, path::PathBuf}; +use std::{ffi::OsStr, fs, path::PathBuf}; use c2rust_transpile::{Diagnostic, ReplaceMode, TranspilerConfig}; @@ -226,28 +226,29 @@ fn main() { tcfg.emit_modules = true }; - let mut created_temp_compile_commands = false; + let mut temp_compile_commands_dir = None; - let compile_commands = if args.compile_commands.len() == 1 - && args.compile_commands[0].extension() == Some(std::ffi::OsStr::new("json")) + let compile_commands = if args + .compile_commands + .iter() + .any(|path| path.extension() == Some(OsStr::new("json"))) { + if args.compile_commands.len() != 1 { + // More than one file provided and at least one is a JSON file + panic!("Compile commands JSON and multiple sources provided. + Exactly one compile_commands.json file should be provided, or a list of source files, but not both."); + } // Only one file provided and it's a JSON file match fs::canonicalize(&args.compile_commands[0]) { Ok(canonical_path) => canonical_path, Err(e) => panic!("Failed to canonicalize path: {:?}", e), } - } else if args - .compile_commands - .iter() - .any(|path| path.extension() == Some(std::ffi::OsStr::new("json"))) - { - // More than one file provided and at least one is a JSON file - panic!("Compile commands JSON and multiple sources provided. - Exactly one compile_commands.json file should be provided, or a list of source files, but not both."); } else { // Handle as a list of source files - created_temp_compile_commands = true; - c2rust_transpile::create_temp_compile_commands(&args.compile_commands) + let (temp_dir, temp_path) = + c2rust_transpile::create_temp_compile_commands(&args.compile_commands); + temp_compile_commands_dir = Some(temp_dir); + temp_path }; let extra_args = args @@ -259,8 +260,8 @@ fn main() { c2rust_transpile::transpile(tcfg, &compile_commands, &extra_args); // Remove the temporary compile_commands.json if it was created - if created_temp_compile_commands { - std::fs::remove_file(&compile_commands) + if let Some(temp) = temp_compile_commands_dir { + temp.close() .expect("Failed to remove temporary compile_commands.json"); } }