Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4e56d15

Browse files
committedMay 26, 2022
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 55 additions & 0 deletions
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

Lines changed: 43 additions & 9 deletions
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)
Please sign in to comment.