Skip to content

Commit 4e56d15

Browse files
Add API to Record Compile Invocation
This commit adds API to record the compilation invocations in order to emit compile_commands.json a.k.a. JSON compilation database. Fixes #497
1 parent f2e1b1c commit 4e56d15

File tree

3 files changed

+100
-9
lines changed

3 files changed

+100
-9
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ exclude = ["/.github"]
1818
edition = "2018"
1919

2020
[dependencies]
21+
serde = { version = "1.0.137", features = ["derive"] }
22+
serde_json = "1.0.81"
2123
jobserver = { version = "0.1.16", optional = true }
2224

2325
[features]

src/json_compilation_database.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::fs::OpenOptions;
2+
use std::path::{Path, PathBuf};
3+
use std::process::Command;
4+
5+
/// An entry for creating a [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
6+
#[derive(serde::Serialize)]
7+
pub struct CompileCommand {
8+
directory: PathBuf,
9+
arguments: Vec<String>,
10+
file: PathBuf,
11+
output: PathBuf,
12+
}
13+
14+
impl CompileCommand {
15+
pub(crate) fn new(cmd: &Command, src: PathBuf, output: PathBuf) -> Self {
16+
let mut arguments = Vec::with_capacity(cmd.get_args().len() + 1);
17+
18+
let program = String::from(cmd.get_program().to_str().unwrap());
19+
arguments.push(
20+
crate::which(&program)
21+
.map(|p| p.to_string_lossy().into_owned())
22+
.map(|p| p.to_string())
23+
.unwrap_or(program),
24+
);
25+
arguments.extend(
26+
cmd.get_args()
27+
.flat_map(std::ffi::OsStr::to_str)
28+
.map(String::from),
29+
);
30+
31+
Self {
32+
// TODO: is the assumption correct?
33+
directory: std::env::current_dir().unwrap(),
34+
arguments,
35+
file: src,
36+
output,
37+
}
38+
}
39+
}
40+
41+
/// Stores the provided list of [compile commands](crate::CompileCommand) as [JSON
42+
/// Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
43+
pub fn store_json_compilation_database<'a, C, P>(commands: C, path: P)
44+
where
45+
C: IntoIterator<Item = &'a CompileCommand>,
46+
P: AsRef<Path>,
47+
{
48+
let file = OpenOptions::new()
49+
.create(true)
50+
.write(true)
51+
.truncate(true)
52+
.open(path)
53+
.unwrap();
54+
serde_json::to_writer_pretty(&file, &commands.into_iter().collect::<Vec<_>>()).unwrap();
55+
}

src/lib.rs

+43-9
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
#![allow(deprecated)]
5757
#![deny(missing_docs)]
5858

59+
pub use crate::json_compilation_database::CompileCommand;
60+
pub use crate::json_compilation_database::store_json_compilation_database;
5961
use std::collections::HashMap;
6062
use std::env;
6163
use std::ffi::{OsStr, OsString};
@@ -81,6 +83,7 @@ mod setup_config;
8183
#[cfg(windows)]
8284
mod vs_instances;
8385

86+
mod json_compilation_database;
8487
pub mod windows_registry;
8588

8689
/// A builder for compilation of a native library.
@@ -943,8 +946,17 @@ impl Build {
943946

944947
/// Run the compiler, generating the file `output`
945948
///
946-
/// This will return a result instead of panicing; see compile() for the complete description.
949+
/// This will return a result instead of panicing; see [compile()](Build::compile) for the complete description.
947950
pub fn try_compile(&self, output: &str) -> Result<(), Error> {
951+
self.try_recorded_compile(output)?;
952+
Ok(())
953+
}
954+
955+
/// Run the compiler, generating the file `output` and provides compile commands for creating
956+
/// [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
957+
///
958+
/// This will return a result instead of panicing; see [recorded_compile()](Build::recorded_compile) for the complete description.
959+
pub fn try_recorded_compile(&self, output: &str) -> Result<Vec<CompileCommand>, Error> {
948960
let mut output_components = Path::new(output).components();
949961
match (output_components.next(), output_components.next()) {
950962
(Some(Component::Normal(_)), None) => {}
@@ -990,7 +1002,7 @@ impl Build {
9901002

9911003
objects.push(Object::new(file.to_path_buf(), obj));
9921004
}
993-
self.compile_objects(&objects)?;
1005+
let entries = self.compile_objects(&objects)?;
9941006
self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?;
9951007

9961008
if self.get_target()?.contains("msvc") {
@@ -1074,7 +1086,7 @@ impl Build {
10741086
}
10751087
}
10761088

1077-
Ok(())
1089+
Ok(entries)
10781090
}
10791091

10801092
/// Run the compiler, generating the file `output`
@@ -1120,6 +1132,26 @@ impl Build {
11201132
}
11211133
}
11221134

1135+
/// Run the compiler, generating the file `output` and provides compile commands for creating
1136+
/// [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html),
1137+
///
1138+
/// ```no_run
1139+
/// let compile_commands = cc::Build::new().file("blobstore.c")
1140+
/// .recorded_compile("blobstore");
1141+
///
1142+
/// cc::store_json_compilation_database(&compile_commands, "target/compilation_database.json");
1143+
/// ```
1144+
///
1145+
/// See [compile()](Build::compile) for the further description.
1146+
pub fn recorded_compile(&self, output: &str) -> Vec<CompileCommand>{
1147+
match self.try_recorded_compile(output) {
1148+
Ok(entries) => entries,
1149+
Err(e) => {
1150+
fail(&e.message);
1151+
}
1152+
}
1153+
}
1154+
11231155
#[cfg(feature = "parallel")]
11241156
fn compile_objects<'me>(&'me self, objs: &[Object]) -> Result<(), Error> {
11251157
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
@@ -1272,14 +1304,15 @@ impl Build {
12721304
}
12731305

12741306
#[cfg(not(feature = "parallel"))]
1275-
fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> {
1307+
fn compile_objects(&self, objs: &[Object]) -> Result<Vec<CompileCommand>, Error> {
1308+
let mut entries = Vec::new();
12761309
for obj in objs {
1277-
self.compile_object(obj)?;
1310+
entries.push(self.compile_object(obj)?);
12781311
}
1279-
Ok(())
1312+
Ok(entries)
12801313
}
12811314

1282-
fn compile_object(&self, obj: &Object) -> Result<(), Error> {
1315+
fn compile_object(&self, obj: &Object) -> Result<CompileCommand, Error> {
12831316
let is_asm = obj.src.extension().and_then(|s| s.to_str()) == Some("asm");
12841317
let target = self.get_target()?;
12851318
let msvc = target.contains("msvc");
@@ -1324,7 +1357,7 @@ impl Build {
13241357
}
13251358

13261359
run(&mut cmd, &name)?;
1327-
Ok(())
1360+
Ok(CompileCommand::new(&cmd, obj.src.clone(), obj.dst.clone()))
13281361
}
13291362

13301363
/// This will return a result instead of panicing; see expand() for the complete description.
@@ -3287,13 +3320,14 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option<
32873320
}
32883321
}
32893322

3290-
fn which(tool: &Path) -> Option<PathBuf> {
3323+
pub(crate) fn which<P>(tool: P) -> Option<PathBuf> where P: AsRef<Path> {
32913324
fn check_exe(exe: &mut PathBuf) -> bool {
32923325
let exe_ext = std::env::consts::EXE_EXTENSION;
32933326
exe.exists() || (!exe_ext.is_empty() && exe.set_extension(exe_ext) && exe.exists())
32943327
}
32953328

32963329
// If |tool| is not just one "word," assume it's an actual path...
3330+
let tool = tool.as_ref();
32973331
if tool.components().count() > 1 {
32983332
let mut exe = PathBuf::from(tool);
32993333
return if check_exe(&mut exe) { Some(exe) } else { None };

0 commit comments

Comments
 (0)