Skip to content

Commit 8b96a17

Browse files
authored
transpile: add --postprocess to run c2rust-postprocess (#1626)
`c2rust-postprocess` is run after transpiling and potentially refactoring (if `--reorganize-definitions` and not `--disable-refactoring`). Since `c2rust-postprocess` is not a Rust binary, it's not always a sibling to the `c2rust`/`c2rust-transpile` binary. It's a bash script that runs a Python script with `uv`. Thus, to find it, we look in `$PATH` first and if it's not there, we assume we're in the `c2rust` repo and `cargo build` has been run and find the relative path of `c2rust-postprocess` (i.e., `target/$profile/../../c2rust-postprocess/c2rust-postprocess`). Similarly to `--reorganize-definitions`/`c2rust-refactor`, this requires `--emit-build-files`, as we need the `crate_file` (e.g., `src/lib.rs`) to run `c2rust-postprocess`. `c2rust-postprocess` can be easily set up to run on a single Rust file, though, so we should be able to drop this requirement later.
2 parents 8978434 + 7ccfb4f commit 8b96a17

7 files changed

Lines changed: 108 additions & 11 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/bin/sh
2-
uv run postprocess "$@"
2+
uv run --project "$(dirname "$0")" postprocess "$@"

c2rust-transpile/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ strum = "0.24"
3939
strum_macros = "0.24"
4040
syn = { version = "2.0", features = ["full", "extra-traits", "parsing", "printing"]}
4141
tempfile = "3.5.0"
42+
which = "4.4.0"
4243

4344
[features]
4445
# Force static linking of LLVM

c2rust-transpile/src/lib.rs

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@ pub mod with_stmts;
1414

1515
use std::collections::HashSet;
1616
use std::fs::{self, File};
17-
use std::io::prelude::*;
17+
use std::io::Write;
1818
use std::path::{Path, PathBuf};
1919
use std::process::Command;
2020
use std::{env, io};
2121

2222
use crate::compile_cmds::CompileCmd;
2323
use c2rust_rust_tools::rustfmt;
24-
use failure::Error;
24+
use failure::{format_err, Error};
2525
use itertools::Itertools;
2626
use log::{info, warn};
2727
use regex::Regex;
2828
use serde_derive::Serialize;
2929
pub use tempfile::TempDir;
30+
use which::which;
3031

3132
use crate::c_ast::Printer;
3233
use crate::c_ast::*;
@@ -106,6 +107,9 @@ pub struct TranspilerConfig {
106107
pub preserve_unused_functions: bool,
107108
pub log_level: log::LevelFilter,
108109

110+
/// Run `c2rust-postprocess` after transpiling and potentially refactoring.
111+
pub postprocess: bool,
112+
109113
// Options that control build files
110114
/// Emit `Cargo.toml` and `lib.rs`
111115
pub emit_build_files: bool,
@@ -438,8 +442,10 @@ pub fn transpile(tcfg: TranspilerConfig, cc_db: &Path, extra_clang_args: &[&str]
438442
top_level_ccfg = Some(ccfg);
439443
} else {
440444
let crate_file = emit_build_files(&tcfg, &build_dir, Some(ccfg), None);
445+
let crate_file = crate_file.as_deref();
441446
reorganize_definitions(&tcfg, &build_dir, crate_file)
442447
.unwrap_or_else(|e| warn!("Reorganizing definitions failed: {}", e));
448+
run_postprocess(&tcfg, &build_dir, crate_file).unwrap_or_else(|e| warn!("{e}"));
443449
workspace_members.push(lcmd_name);
444450
}
445451
}
@@ -453,8 +459,10 @@ pub fn transpile(tcfg: TranspilerConfig, cc_db: &Path, extra_clang_args: &[&str]
453459
if tcfg.emit_build_files {
454460
let crate_file =
455461
emit_build_files(&tcfg, &build_dir, top_level_ccfg, Some(workspace_members));
462+
let crate_file = crate_file.as_deref();
456463
reorganize_definitions(&tcfg, &build_dir, crate_file)
457464
.unwrap_or_else(|e| warn!("Reorganizing definitions failed: {}", e));
465+
run_postprocess(&tcfg, &build_dir, crate_file).unwrap_or_else(|e| warn!("{e}"));
458466
}
459467

460468
tcfg.check_if_all_binaries_used(&transpiled_modules);
@@ -535,7 +543,7 @@ fn invoke_refactor(build_dir: &Path) -> Result<(), Error> {
535543
fn reorganize_definitions(
536544
tcfg: &TranspilerConfig,
537545
build_dir: &Path,
538-
crate_file: Option<PathBuf>,
546+
crate_file: Option<&Path>,
539547
) -> Result<(), Error> {
540548
// We only run the reorganization refactoring if we emitted a fresh crate file
541549
if crate_file.is_none() || tcfg.disable_refactoring || !tcfg.reorganize_definitions {
@@ -558,7 +566,80 @@ fn reorganize_definitions(
558566
Ok(())
559567
}
560568

561-
/// Transpile one input C file, writing transpilation output to the filesystem.
569+
/// Invokes `c2rust-postprocess`.
570+
///
571+
/// This assumes the subcommand executable is either in `$PATH`
572+
/// or in the same relative directory as it is in the repo
573+
/// and the current executable is in `target/$profile/`.
574+
fn invoke_postprocess(crate_file: &Path, build_dir: &Path) -> Result<(), Error> {
575+
let subcommand = "c2rust-postprocess";
576+
let subcommand_path_buf;
577+
let subcommand_path = match which(subcommand) {
578+
Ok(_) => Path::new(subcommand),
579+
Err(_) => {
580+
let current_exe = env::current_exe()?;
581+
let current_exe_dir = current_exe.parent();
582+
let current_exe = current_exe.display();
583+
let target_dir = current_exe_dir
584+
.ok_or_else(|| format_err!("no parent of {current_exe}"))?
585+
.parent()
586+
.ok_or_else(|| format_err!("no grandparent of {current_exe}"))?;
587+
let target_dir_name = target_dir
588+
.file_name()
589+
.unwrap_or_default()
590+
.to_str()
591+
.unwrap_or_default();
592+
if target_dir_name != "target" {
593+
Err(format_err!(
594+
"{current_exe} not in `target/$profile/` and {subcommand} not in $PATH"
595+
))?;
596+
}
597+
let repo_root = target_dir
598+
.parent()
599+
.ok_or_else(|| format_err!("no repo root ancestor of {current_exe}"))?;
600+
subcommand_path_buf = repo_root.join(subcommand).join(subcommand);
601+
&subcommand_path_buf
602+
}
603+
};
604+
605+
let mut cmd = Command::new(subcommand_path);
606+
cmd.arg("--update-rust")
607+
.arg(crate_file)
608+
.current_dir(build_dir);
609+
let status = cmd
610+
.status()
611+
.map_err(|e| {
612+
let path = subcommand_path.display();
613+
failure::format_err!("unable to run {path}: {e}\nNote that {subcommand} must be installed separately from c2rust and c2rust-transpile.\
614+
It must be either installed in $PATH or c2rust/c2rust-transpile must be in `target/$profile/` from the c2rust repo.")
615+
})?;
616+
617+
if !status.success() {
618+
Err(format_err!("postprocess failed: {cmd:?}"))?;
619+
}
620+
621+
Ok(())
622+
}
623+
624+
fn run_postprocess(
625+
tcfg: &TranspilerConfig,
626+
build_dir: &Path,
627+
crate_file: Option<&Path>,
628+
) -> Result<(), Error> {
629+
let crate_file = match crate_file {
630+
Some(crate_file) => crate_file,
631+
None => return Ok(()),
632+
};
633+
if !tcfg.postprocess {
634+
return Ok(());
635+
}
636+
637+
invoke_postprocess(crate_file, build_dir)?;
638+
639+
Ok(())
640+
}
641+
642+
/// Transpiles one input C file, writing transpilation output to the filesystem.
562643
fn transpile_single(
563644
tcfg: &TranspilerConfig,
564645
input_path: &Path,

c2rust-transpile/tests/snapshots.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fn config() -> TranspilerConfig {
5050
disable_refactoring: false,
5151
preserve_unused_functions: false,
5252
log_level: log::LevelFilter::Warn,
53+
postprocess: false,
5354
emit_build_files: false,
5455
binaries: Vec::new(),
5556
c2rust_dir: Some(

c2rust/src/bin/c2rust-transpile.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ struct Args {
139139
#[clap(short = 'r', long)]
140140
reorganize_definitions: bool,
141141

142+
/// Run `c2rust-postprocess` after transpiling and potentially refactoring.
143+
#[clap(long)]
144+
postprocess: bool,
145+
142146
/// Extra arguments to pass to clang frontend during parsing the input C file
143147
#[clap(multiple = true, last(true))]
144148
extra_clang_args: Vec<String>,
@@ -288,6 +292,7 @@ fn main() {
288292
overwrite_existing: args.overwrite_existing,
289293
reduce_type_annotations: args.reduce_type_annotations,
290294
reorganize_definitions: args.reorganize_definitions,
295+
postprocess: args.postprocess,
291296
emit_modules: args.emit_modules,
292297
emit_build_files: args.emit_build_files,
293298
c2rust_dir: args.c2rust_dir,

c2rust/src/main.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ impl SubCommand {
2424
/// Find all [`SubCommand`]s adjacent to the current (`c2rust`) executable.
2525
/// They are of the form `c2rust-{name}`.
2626
pub fn find_all() -> anyhow::Result<Vec<Self>> {
27+
// TODO Add support for finding `c2rust-postprocess`, which won't be directly adjacent.
2728
let c2rust = env::current_exe()?;
2829
let c2rust_name = c2rust
2930
.file_name()
@@ -60,12 +61,19 @@ impl SubCommand {
6061
/// Get all known [`SubCommand`]s. These have no [`SubCommand::path`].
6162
/// Even if the subcommand executables aren't there, we can still suggest them.
6263
pub fn known() -> impl Iterator<Item = Self> {
63-
["transpile", "refactor", "instrument", "pdg", "analyze"]
64-
.into_iter()
65-
.map(|name| Self {
66-
path: None,
67-
name: name.into(),
68-
})
64+
[
65+
"transpile",
66+
"refactor",
67+
"postprocess",
68+
"instrument",
69+
"pdg",
70+
"analyze",
71+
]
72+
.into_iter()
73+
.map(|name| Self {
74+
path: None,
75+
name: name.into(),
76+
})
6977
}
7078

7179
/// Get all known ([`Self::known`]) and actual, found ([`Self::find_all`]) subcommands,

0 commit comments

Comments
 (0)