Skip to content

Commit

Permalink
support c2rust <path/to/*.c> in lieu of compile_commands.json (#1037
Browse files Browse the repository at this point in the history
)

* Fixes #1033.

This adds support for transpiling C sources without needing to generate
a `compile_commands.json` with something such as `bear` or `cmake`. The
way it works is that a temporary `temp_compile_commands` file is
created. One alternative to this approach would have been to alter the
AST exporter to use a `FixedCompilationDatabase` but it seemed simpler
to support this feature from the front end.
  • Loading branch information
aneksteind authored Nov 17, 2023
2 parents 3be0dfa + 985d71f commit ba4b1f5
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 23 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,15 @@ run the `c2rust` tool with the `transpile` subcommand:
c2rust transpile compile_commands.json
```
`c2rust` also supports a trivial transpile of source files, e.g.:
```sh
c2rust transpile project/*.c project/*.h
```
(The `c2rust refactor` tool was also available for refactoring Rust code, see [refactoring](./c2rust-refactor/), but is now being replaced by a more robust way to refactor.)
The translator requires the exact compiler commands used to build the C code.
For non-trivial projects, the translator requires the exact compiler commands used to build the C code.
This information is provided via a [compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html) file named `compile_commands.json`.
(Read more about [compilation databases here](https://sarcasm.github.io/notes/dev/compilation-database.html)).
Many build systems can automatically generate this file;
Expand Down
5 changes: 5 additions & 0 deletions c2rust-transpile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ The transpiler module is invoked using the `transpile` sub-command of `c2rust`:

c2rust transpile [args] compile_commands.json [-- extra-clang-args]

The transpiler can also be given source files directly, which is ideal for projects that are trivially compiled, e.g.:

c2rust transpile [args] *.h *.c [-- extra-clang-args]


The following arguments control the basic transpiler behavior:

- `--emit-modules` - Emit each translated Rust file as a module (the default is
Expand Down
12 changes: 6 additions & 6 deletions c2rust-transpile/src/compile_cmds/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ use std::rc::Rc;
use failure::Error;
use log::warn;
use regex::Regex;
use serde_derive::Deserialize;
use serde_derive::{Deserialize, Serialize};

#[derive(Deserialize, Debug, Default, Clone)]
#[derive(Deserialize, Serialize, Debug, Default, Clone)]
pub struct CompileCmd {
/// The working directory of the compilation. All paths specified in the command
/// or file fields must be either absolute or relative to this directory.
directory: PathBuf,
pub directory: PathBuf,
/// The main translation unit source processed by this compilation step. This is
/// used by tools as the key into the compilation database. There can be multiple
/// command objects for the same file, for example if the same source file is compiled
Expand All @@ -23,13 +23,13 @@ pub struct CompileCmd {
/// the build system uses. Parameters use shell quoting and shell escaping of quotes,
/// with ‘"’ and ‘\’ being the only special characters. Shell expansion is not supported.
#[serde(skip_deserializing)]
_command: Option<String>,
pub command: Option<String>,
/// The compile command executed as list of strings. Either arguments or command is required.
#[serde(default, skip_deserializing)]
_arguments: Vec<String>,
pub arguments: Vec<String>,
/// The name of the output created by this compilation step. This field is optional. It can
/// be used to distinguish different processing modes of the same input file.
output: Option<String>,
pub output: Option<String>,
}

impl CompileCmd {
Expand Down
39 changes: 36 additions & 3 deletions c2rust-transpile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process;

use crate::compile_cmds::CompileCmd;
use failure::Error;
use itertools::Itertools;
use log::{info, warn};
Expand Down Expand Up @@ -225,12 +226,44 @@ 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");
let compile_commands: Vec<CompileCmd> = sources
.iter()
.map(|source_file| {
let absolute_path = fs::canonicalize(source_file)
.unwrap_or_else(|_| panic!("Could not canonicalize {}", source_file.display()));

CompileCmd {
directory: PathBuf::from("."),
file: absolute_path.clone(),
arguments: vec![
"clang".to_string(),
absolute_path.to_str().unwrap().to_owned(),
],
command: None,
output: None,
}
})
.collect();

let json_content = serde_json::to_string(&compile_commands).unwrap();
let mut file =
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
}

/// Main entry point to transpiler. Called from CLI tools with the result of
/// clap::App::get_matches().
pub fn transpile(tcfg: TranspilerConfig, cc_db: &Path, extra_clang_args: &[&str]) {
diagnostics::init(tcfg.enabled_warnings.clone(), tcfg.log_level);

let lcmds = get_compile_commands(cc_db, &tcfg.filter).unwrap_or_else(|_| {
let build_dir = get_build_dir(&tcfg, cc_db);

let lcmds = get_compile_commands(&cc_db, &tcfg.filter).unwrap_or_else(|_| {
panic!(
"Could not parse compile commands from {}",
cc_db.to_string_lossy()
Expand All @@ -246,7 +279,7 @@ pub fn transpile(tcfg: TranspilerConfig, cc_db: &Path, extra_clang_args: &[&str]
let mut workspace_members = vec![];
let mut num_transpiled_files = 0;
let mut transpiled_modules = Vec::new();
let build_dir = get_build_dir(&tcfg, cc_db);

for lcmd in &lcmds {
let cmds = &lcmd.cmd_inputs;
let lcmd_name = lcmd
Expand Down Expand Up @@ -297,7 +330,7 @@ pub fn transpile(tcfg: TranspilerConfig, cc_db: &Path, extra_clang_args: &[&str]
cmd.abs_file(),
&ancestor_path,
&build_dir,
cc_db,
&cc_db,
&clang_args,
)
})
Expand Down
45 changes: 32 additions & 13 deletions c2rust/src/bin/c2rust-transpile.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clap::{Parser, ValueEnum};
use log::LevelFilter;
use regex::Regex;
use std::path::{Path, PathBuf};
use std::{fs, path::PathBuf};

use c2rust_transpile::{Diagnostic, ReplaceMode, TranspilerConfig};

Expand Down Expand Up @@ -84,9 +84,9 @@ struct Args {
#[clap(long = "ddebug-labels")]
debug_labels: bool,

/// Input compile_commands.json file
#[clap()]
compile_commands: PathBuf,
/// Path to compile_commands.json, or a list of source files
#[clap(parse(from_os_str), multiple_values = true)]
compile_commands: Vec<PathBuf>,

/// How to handle violated invariants or invalid code
#[clap(long, value_enum, default_value_t = InvalidCodes::CompileError)]
Expand Down Expand Up @@ -129,7 +129,7 @@ struct Args {
reorganize_definitions: bool,

/// Extra arguments to pass to clang frontend during parsing the input C file
#[clap(multiple = true)]
#[clap(multiple = true, last(true))]
extra_clang_args: Vec<String>,

/// Enable the specified warning (all enables all warnings)
Expand Down Expand Up @@ -226,19 +226,38 @@ fn main() {
tcfg.emit_modules = true
};

let cc_json_path = Path::new(&args.compile_commands);
let cc_json_path = cc_json_path.canonicalize().unwrap_or_else(|_| {
panic!(
"Could not find compile_commands.json file at path: {}",
cc_json_path.display()
)
});
let compile_commands = if args.compile_commands.len() == 1
&& args.compile_commands[0].extension() == Some(std::ffi::OsStr::new("json"))
{
// 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
c2rust_transpile::create_temp_compile_commands(&args.compile_commands)
};

let extra_args = args
.extra_clang_args
.iter()
.map(AsRef::as_ref)
.collect::<Vec<_>>();

c2rust_transpile::transpile(tcfg, &cc_json_path, &extra_args);
c2rust_transpile::transpile(tcfg, &compile_commands, &extra_args);

// Remove the temporary compile_commands.json if it was created
if args.compile_commands.len() > 0 {
std::fs::remove_file(&compile_commands)
.expect("Failed to remove temporary compile_commands.json");
}
}

0 comments on commit ba4b1f5

Please sign in to comment.