diff --git a/Cargo.toml b/Cargo.toml index 227e2569e..093de1121 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/src/json_compilation_database.rs b/src/json_compilation_database.rs new file mode 100644 index 000000000..43726587d --- /dev/null +++ b/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, + 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, + P: AsRef, +{ + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path) + .unwrap(); + serde_json::to_writer_pretty(&file, &commands.into_iter().collect::>()).unwrap(); +} diff --git a/src/lib.rs b/src/lib.rs index 527d1d67d..67590b643 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; @@ -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. @@ -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, Error> { let mut output_components = Path::new(output).components(); match (output_components.next(), output_components.next()) { (Some(Component::Normal(_)), None) => {} @@ -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") { @@ -1074,7 +1086,7 @@ impl Build { } } - Ok(()) + Ok(entries) } /// Run the compiler, generating the file `output` @@ -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 { + 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}; @@ -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, 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 { let is_asm = obj.src.extension().and_then(|s| s.to_str()) == Some("asm"); let target = self.get_target()?; let msvc = target.contains("msvc"); @@ -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. @@ -3287,13 +3320,17 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option< } } -fn which(tool: &Path) -> Option { +pub(crate) fn which

(tool: P) -> Option +where + P: AsRef, +{ 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 };