Skip to content

Commit

Permalink
Add API to Record Compile Invocation
Browse files Browse the repository at this point in the history
This commit adds API to record the compilation invocations in order to
emit compile_commands.json a.k.a. JSON compilation database.

Fixes #497
  • Loading branch information
schrieveslaach committed May 26, 2022
1 parent f2e1b1c commit 109da8d
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -18,6 +18,8 @@ exclude = ["/.github"]
edition = "2018"

[dependencies]
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.81"
jobserver = { version = "0.1.16", optional = true }

[features]
Expand Down
55 changes: 55 additions & 0 deletions src/json_compilation_database.rs
@@ -0,0 +1,55 @@
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
use std::process::Command;

/// An entry for creating a [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
#[derive(serde::Serialize)]
pub struct CompileCommand {
directory: PathBuf,
arguments: Vec<String>,
file: PathBuf,
output: PathBuf,
}

impl CompileCommand {
pub(crate) fn new(cmd: &Command, src: PathBuf, output: PathBuf) -> Self {
let mut arguments = Vec::with_capacity(cmd.get_args().len() + 1);

let program = String::from(cmd.get_program().to_str().unwrap());
arguments.push(
crate::which(&program)
.map(|p| p.to_string_lossy().into_owned())
.map(|p| p.to_string())
.unwrap_or(program),
);
arguments.extend(
cmd.get_args()
.flat_map(std::ffi::OsStr::to_str)
.map(String::from),
);

Self {
// TODO: is the assumption correct?
directory: std::env::current_dir().unwrap(),
arguments,
file: src,
output,
}
}
}

/// Stores the provided list of [compile commands](crate::CompileCommand) as [JSON
/// Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
pub fn store_json_compilation_database<'a, C, P>(commands: C, path: P)
where
C: IntoIterator<Item = &'a CompileCommand>,
P: AsRef<Path>,
{
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.unwrap();
serde_json::to_writer_pretty(&file, &commands.into_iter().collect::<Vec<_>>()).unwrap();
}
55 changes: 46 additions & 9 deletions src/lib.rs
Expand Up @@ -56,6 +56,8 @@
#![allow(deprecated)]
#![deny(missing_docs)]

pub use crate::json_compilation_database::store_json_compilation_database;
pub use crate::json_compilation_database::CompileCommand;
use std::collections::HashMap;
use std::env;
use std::ffi::{OsStr, OsString};
Expand All @@ -81,6 +83,7 @@ mod setup_config;
#[cfg(windows)]
mod vs_instances;

mod json_compilation_database;
pub mod windows_registry;

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

/// Run the compiler, generating the file `output`
///
/// This will return a result instead of panicing; see compile() for the complete description.
/// This will return a result instead of panicing; see [compile()](Build::compile) for the complete description.
pub fn try_compile(&self, output: &str) -> Result<(), Error> {
self.try_recorded_compile(output)?;
Ok(())
}

/// Run the compiler, generating the file `output` and provides compile commands for creating
/// [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
///
/// This will return a result instead of panicing; see [recorded_compile()](Build::recorded_compile) for the complete description.
pub fn try_recorded_compile(&self, output: &str) -> Result<Vec<CompileCommand>, Error> {
let mut output_components = Path::new(output).components();
match (output_components.next(), output_components.next()) {
(Some(Component::Normal(_)), None) => {}
Expand Down Expand Up @@ -990,7 +1002,7 @@ impl Build {

objects.push(Object::new(file.to_path_buf(), obj));
}
self.compile_objects(&objects)?;
let entries = self.compile_objects(&objects)?;
self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?;

if self.get_target()?.contains("msvc") {
Expand Down Expand Up @@ -1074,7 +1086,7 @@ impl Build {
}
}

Ok(())
Ok(entries)
}

/// Run the compiler, generating the file `output`
Expand Down Expand Up @@ -1120,6 +1132,26 @@ impl Build {
}
}

/// Run the compiler, generating the file `output` and provides compile commands for creating
/// [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html),
///
/// ```no_run
/// let compile_commands = cc::Build::new().file("blobstore.c")
/// .recorded_compile("blobstore");
///
/// cc::store_json_compilation_database(&compile_commands, "target/compilation_database.json");
/// ```
///
/// See [compile()](Build::compile) for the further description.
pub fn recorded_compile(&self, output: &str) -> Vec<CompileCommand> {
match self.try_recorded_compile(output) {
Ok(entries) => entries,
Err(e) => {
fail(&e.message);
}
}
}

#[cfg(feature = "parallel")]
fn compile_objects<'me>(&'me self, objs: &[Object]) -> Result<(), Error> {
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
Expand Down Expand Up @@ -1272,14 +1304,15 @@ impl Build {
}

#[cfg(not(feature = "parallel"))]
fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> {
fn compile_objects(&self, objs: &[Object]) -> Result<Vec<CompileCommand>, Error> {
let mut entries = Vec::new();
for obj in objs {
self.compile_object(obj)?;
entries.push(self.compile_object(obj)?);
}
Ok(())
Ok(entries)
}

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

run(&mut cmd, &name)?;
Ok(())
Ok(CompileCommand::new(&cmd, obj.src.clone(), obj.dst.clone()))
}

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

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

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

0 comments on commit 109da8d

Please sign in to comment.