,
}
impl EntryPredicateBuilder {
/// Creates an entry predicate from the builder.
pub(super) fn build(self) -> EntryPredicate {
match self.candidate {
Some(predicate) => predicate,
None => Box::new(|_: &Entry| true),
}
}
/// Construct a predicate builder that is empty.
#[inline]
pub(crate) fn new() -> Self {
Self { candidate: None }
}
/// Construct a predicate builder that implements a predicate.
#[inline]
fn from(predicate: P) -> Self
where
P: FnMut(&Entry) -> bool + 'static,
{
Self {
candidate: Some(Box::new(predicate)),
}
}
/// Create a predicate that filters out entries
/// that are not using any of the given source paths.
pub(super) fn filter_by_source_path(path: &Path) -> Self {
let owned_path = path.to_owned();
Self::from(move |entry| entry.file.starts_with(owned_path.clone()))
}
/// Create a predicate that filters out entries
/// that source file does not exist.
pub(super) fn filter_by_source_existence(only_existing: bool) -> Self {
if only_existing {
Self::from(|entry| entry.file.is_file())
} else {
Self::new()
}
}
/// Create a predicate that filters out entries
/// that are already in the compilation database based on their hash.
pub(super) fn filter_duplicate_entries(
hash_function: impl Fn(&Entry) -> u64 + 'static,
) -> Self {
let mut have_seen = HashSet::new();
Self::from(move |entry| {
let hash = hash_function(entry);
if !have_seen.contains(&hash) {
have_seen.insert(hash);
true
} else {
false
}
})
}
}
/// Implement the AND operator for combining predicates.
impl std::ops::BitAnd for EntryPredicateBuilder {
type Output = EntryPredicateBuilder;
fn bitand(self, rhs: Self) -> Self::Output {
match (self.candidate, rhs.candidate) {
(None, None) => EntryPredicateBuilder::new(),
(None, some) => EntryPredicateBuilder { candidate: some },
(some, None) => EntryPredicateBuilder { candidate: some },
(Some(mut lhs), Some(mut rhs)) => EntryPredicateBuilder::from(move |entry| {
let result = lhs(entry);
if result {
rhs(entry)
} else {
result
}
}),
}
}
}
/// Implement the NOT operator for combining predicates.
impl std::ops::Not for EntryPredicateBuilder {
type Output = EntryPredicateBuilder;
fn not(self) -> Self::Output {
match self.candidate {
Some(mut original) => Self::from(move |entry| {
let result = original(entry);
!result
}),
None => Self::new(),
}
}
}
/// Create a hash function that is using the given fields to calculate the hash of an entry.
pub(super) fn create_hash(fields: &[config::OutputFields]) -> impl Fn(&Entry) -> u64 + 'static {
let owned_fields: Vec = fields.to_vec();
move |entry: &Entry| {
let mut hasher = DefaultHasher::new();
for field in &owned_fields {
match field {
config::OutputFields::Directory => entry.directory.hash(&mut hasher),
config::OutputFields::File => entry.file.hash(&mut hasher),
config::OutputFields::Arguments => entry.arguments.hash(&mut hasher),
config::OutputFields::Output => entry.output.hash(&mut hasher),
}
}
hasher.finish()
}
}
#[cfg(test)]
mod sources_test {
use super::*;
use crate::vec_of_strings;
use std::path::PathBuf;
#[test]
fn test_filter_by_source_paths() {
let input: Vec = vec![
Entry {
file: PathBuf::from("/home/user/project/source/source.c"),
arguments: vec_of_strings!["cc", "-c", "source.c"],
directory: PathBuf::from("/home/user/project"),
output: None,
},
Entry {
file: PathBuf::from("/home/user/project/test/source.c"),
arguments: vec_of_strings!["cc", "-c", "test.c"],
directory: PathBuf::from("/home/user/project"),
output: None,
},
];
let expected: Vec = vec![input[0].clone()];
let config = config::SourceFilter {
only_existing_files: false,
paths: vec![
config::DirectoryFilter {
path: PathBuf::from("/home/user/project/source"),
ignore: config::Ignore::Never,
},
config::DirectoryFilter {
path: PathBuf::from("/home/user/project/test"),
ignore: config::Ignore::Always,
},
],
};
let sut: EntryPredicate = From::from(&config);
let result: Vec = input.into_iter().filter(sut).collect();
assert_eq!(result, expected);
}
}
#[cfg(test)]
mod existence_test {
use super::*;
use crate::vec_of_strings;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
#[test]
fn test_duplicate_detection_works() {
let input: Vec = vec![
Entry {
file: PathBuf::from("/home/user/project/source.c"),
arguments: vec_of_strings!["cc", "-c", "source.c"],
directory: PathBuf::from("/home/user/project"),
output: Some(PathBuf::from("/home/user/project/source.o")),
},
Entry {
file: PathBuf::from("/home/user/project/source.c"),
arguments: vec_of_strings!["cc", "-c", "-Wall", "source.c"],
directory: PathBuf::from("/home/user/project"),
output: Some(PathBuf::from("/home/user/project/source.o")),
},
Entry {
file: PathBuf::from("/home/user/project/source.c"),
arguments: vec_of_strings!["cc", "-c", "source.c", "-o", "test.o"],
directory: PathBuf::from("/home/user/project"),
output: Some(PathBuf::from("/home/user/project/test.o")),
},
];
let expected: Vec = vec![input[0].clone(), input[2].clone()];
let hash_function = |entry: &Entry| {
let mut hasher = DefaultHasher::new();
entry.file.hash(&mut hasher);
entry.output.hash(&mut hasher);
hasher.finish()
};
let sut: EntryPredicate =
EntryPredicateBuilder::filter_duplicate_entries(hash_function).build();
let result: Vec = input.into_iter().filter(sut).collect();
assert_eq!(result, expected);
}
}
#[cfg(test)]
mod create_hash_tests {
use super::*;
use crate::vec_of_strings;
use std::path::PathBuf;
#[test]
fn test_create_hash_with_directory_field() {
let entry = create_test_entry();
let fields = vec![config::OutputFields::Directory];
let hash_function = create_hash(&fields);
let hash = hash_function(&entry);
let mut hasher = DefaultHasher::new();
entry.directory.hash(&mut hasher);
let expected_hash = hasher.finish();
assert_eq!(hash, expected_hash);
}
#[test]
fn test_create_hash_with_file_field() {
let entry = create_test_entry();
let fields = vec![config::OutputFields::File];
let hash_function = create_hash(&fields);
let hash = hash_function(&entry);
let mut hasher = DefaultHasher::new();
entry.file.hash(&mut hasher);
let expected_hash = hasher.finish();
assert_eq!(hash, expected_hash);
}
#[test]
fn test_create_hash_with_arguments_field() {
let entry = create_test_entry();
let fields = vec![config::OutputFields::Arguments];
let hash_function = create_hash(&fields);
let hash = hash_function(&entry);
let mut hasher = DefaultHasher::new();
entry.arguments.hash(&mut hasher);
let expected_hash = hasher.finish();
assert_eq!(hash, expected_hash);
}
#[test]
fn test_create_hash_with_output_field() {
let entry = create_test_entry();
let fields = vec![config::OutputFields::Output];
let hash_function = create_hash(&fields);
let hash = hash_function(&entry);
let mut hasher = DefaultHasher::new();
entry.output.hash(&mut hasher);
let expected_hash = hasher.finish();
assert_eq!(hash, expected_hash);
}
#[test]
fn test_create_hash_with_multiple_fields() {
let entry = create_test_entry();
let fields = vec![
config::OutputFields::Directory,
config::OutputFields::File,
config::OutputFields::Arguments,
config::OutputFields::Output,
];
let hash_function = create_hash(&fields);
let hash = hash_function(&entry);
let mut hasher = DefaultHasher::new();
entry.directory.hash(&mut hasher);
entry.file.hash(&mut hasher);
entry.arguments.hash(&mut hasher);
entry.output.hash(&mut hasher);
let expected_hash = hasher.finish();
assert_eq!(hash, expected_hash);
}
fn create_test_entry() -> Entry {
Entry {
file: PathBuf::from("/home/user/project/source.c"),
arguments: vec_of_strings!["cc", "-c", "source.c"],
directory: PathBuf::from("/home/user/project"),
output: Some(PathBuf::from("/home/user/project/source.o")),
}
}
}
#[cfg(test)]
mod bitand_tests {
use super::*;
use crate::vec_of_strings;
use std::path::PathBuf;
#[test]
fn test_bitand_both_predicates_true() {
let input = create_test_entries();
let predicate1 = EntryPredicateBuilder::from(|_: &Entry| true);
let predicate2 = EntryPredicateBuilder::from(|_: &Entry| true);
let combined_predicate = (predicate1 & predicate2).build();
let result: Vec = input.into_iter().filter(combined_predicate).collect();
assert_eq!(result.len(), 1);
}
#[test]
fn test_bitand_first_predicate_false() {
let input = create_test_entries();
let predicate1 = EntryPredicateBuilder::from(|_: &Entry| false);
let predicate2 = EntryPredicateBuilder::from(|_: &Entry| true);
let combined_predicate = (predicate1 & predicate2).build();
let result: Vec = input.into_iter().filter(combined_predicate).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_bitand_second_predicate_false() {
let input = create_test_entries();
let predicate1 = EntryPredicateBuilder::from(|_: &Entry| true);
let predicate2 = EntryPredicateBuilder::from(|_: &Entry| false);
let combined_predicate = (predicate1 & predicate2).build();
let result: Vec = input.into_iter().filter(combined_predicate).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_bitand_both_predicates_false() {
let input = create_test_entries();
let predicate1 = EntryPredicateBuilder::from(|_: &Entry| false);
let predicate2 = EntryPredicateBuilder::from(|_: &Entry| false);
let combined_predicate = (predicate1 & predicate2).build();
let result: Vec = input.into_iter().filter(combined_predicate).collect();
assert_eq!(result.len(), 0);
}
fn create_test_entries() -> Vec {
vec![Entry {
file: PathBuf::from("/home/user/project/source/source.c"),
arguments: vec_of_strings!["cc", "-c", "source.c"],
directory: PathBuf::from("/home/user/project"),
output: None,
}]
}
}
#[cfg(test)]
mod not_tests {
use super::*;
use crate::vec_of_strings;
use std::path::PathBuf;
#[test]
fn test_not_predicate_true() {
let input = create_test_entries();
let predicate = EntryPredicateBuilder::from(|_: &Entry| true);
let not_predicate = (!predicate).build();
let result: Vec = input.into_iter().filter(not_predicate).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_not_predicate_false() {
let input = create_test_entries();
let predicate = EntryPredicateBuilder::from(|_: &Entry| false);
let not_predicate = (!predicate).build();
let result: Vec = input.into_iter().filter(not_predicate).collect();
assert_eq!(result.len(), 1);
}
fn create_test_entries() -> Vec {
vec![Entry {
file: PathBuf::from("/home/user/project/source/source.c"),
arguments: vec_of_strings!["cc", "-c", "source.c"],
directory: PathBuf::from("/home/user/project"),
output: None,
}]
}
}
}
rizsotto-Bear-14c2e01/rust/bear/src/output/formatter.rs 0000664 0000000 0000000 00000022245 14767742337 0023236 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use crate::output::clang::Entry;
use crate::{config, semantic};
use anyhow::anyhow;
use path_absolutize::Absolutize;
use std::borrow::Cow;
use std::io;
use std::path::{Path, PathBuf};
pub struct EntryFormatter {
drop_output_field: bool,
path_format: config::PathFormat,
}
impl From<&config::Format> for EntryFormatter {
/// Create a formatter from the configuration.
fn from(config: &config::Format) -> Self {
let drop_output_field = config.drop_output_field;
let path_format = config.paths_as.clone();
Self {
drop_output_field,
path_format,
}
}
}
impl EntryFormatter {
/// Convert the compiler calls into entries.
///
/// The conversion is done by converting the compiler passes into entries.
/// Errors are logged and ignored. The entries format is controlled by the configuration.
pub(crate) fn apply(&self, compiler_call: semantic::CompilerCall) -> Vec {
let semantic::CompilerCall {
compiler,
working_dir,
passes,
} = compiler_call;
passes
.into_iter()
.map(|pass| self.try_convert_from_pass(&working_dir, &compiler, pass))
// We are here to log the error.
.map(|result| result.map_err(|error| log::info!("{}", error)))
.filter_map(Result::ok)
.collect()
}
/// Creates a single entry from a compiler pass if possible.
///
/// The preprocess pass is ignored, and the compile pass is converted into an entry.
///
/// The file and directory paths are converted into fully qualified paths when required.
fn try_convert_from_pass(
&self,
working_dir: &Path,
compiler: &Path,
pass: semantic::CompilerPass,
) -> anyhow::Result {
match pass {
semantic::CompilerPass::Preprocess => {
Err(anyhow!("preprocess pass should not show up in results"))
}
semantic::CompilerPass::Compile {
source,
output,
flags,
} => {
let output_clone = output.clone();
let output_result = match output.filter(|_| !self.drop_output_field) {
None => None,
Some(candidate) => {
let x = self.format_path(candidate.as_path(), working_dir)?;
Some(PathBuf::from(x))
}
};
Ok(Entry {
file: PathBuf::from(self.format_path(source.as_path(), working_dir)?),
directory: working_dir.to_path_buf(),
output: output_result,
arguments: Self::format_arguments(compiler, &source, &flags, output_clone)?,
})
}
}
}
/// Reconstruct the arguments for the compiler call.
///
/// It is not the same as the command line arguments, because the compiler call is
/// decomposed into a separate lists of arguments. To assemble from the parts will
/// not necessarily result in the same command line arguments. One example for that
/// is the multiple source files are treated as separate compiler calls. Another
/// thing that can change is the order of the arguments.
fn format_arguments(
compiler: &Path,
source: &Path,
flags: &[String],
output: Option,
) -> anyhow::Result, anyhow::Error> {
let mut arguments: Vec = vec![];
// Assemble the arguments as it would be for a single source file.
arguments.push(into_string(compiler)?);
for flag in flags {
arguments.push(flag.clone());
}
if let Some(file) = output {
arguments.push(String::from("-o"));
arguments.push(into_string(file.as_path())?)
}
arguments.push(into_string(source)?);
Ok(arguments)
}
fn format_path<'a>(&self, path: &'a Path, root: &Path) -> io::Result> {
// Will compute the absolute path if needed.
let absolute = || {
if path.is_absolute() {
path.absolutize()
} else {
path.absolutize_from(root)
}
};
match self.path_format {
config::PathFormat::Original => Ok(Cow::from(path)),
config::PathFormat::Absolute => absolute(),
config::PathFormat::Canonical => absolute()?.canonicalize().map(Cow::from),
}
}
}
fn into_string(path: &Path) -> anyhow::Result {
path.to_path_buf()
.into_os_string()
.into_string()
.map_err(|_| anyhow!("Path can't be encoded to UTF"))
}
#[cfg(test)]
mod test {
use super::*;
use crate::vec_of_strings;
#[test]
fn test_non_compilations() {
let input = semantic::CompilerCall {
compiler: PathBuf::from("/usr/bin/cc"),
working_dir: PathBuf::from("/home/user"),
passes: vec![semantic::CompilerPass::Preprocess],
};
let format = config::Format {
command_as_array: true,
drop_output_field: false,
paths_as: config::PathFormat::Original,
};
let sut: EntryFormatter = (&format).into();
let result = sut.apply(input);
let expected: Vec = vec![];
assert_eq!(expected, result);
}
#[test]
fn test_single_source_compilation() {
let input = semantic::CompilerCall {
compiler: PathBuf::from("/usr/bin/clang"),
working_dir: PathBuf::from("/home/user"),
passes: vec![semantic::CompilerPass::Compile {
source: PathBuf::from("source.c"),
output: Some(PathBuf::from("source.o")),
flags: vec_of_strings!["-Wall"],
}],
};
let format = config::Format {
command_as_array: true,
drop_output_field: false,
paths_as: config::PathFormat::Original,
};
let sut: EntryFormatter = (&format).into();
let result = sut.apply(input);
let expected = vec![Entry {
directory: PathBuf::from("/home/user"),
file: PathBuf::from("source.c"),
arguments: vec_of_strings!["/usr/bin/clang", "-Wall", "-o", "source.o", "source.c"],
output: Some(PathBuf::from("source.o")),
}];
assert_eq!(expected, result);
}
#[test]
fn test_multiple_sources_compilation() {
let input = compiler_call_with_multiple_passes();
let format = config::Format {
command_as_array: true,
drop_output_field: true,
paths_as: config::PathFormat::Original,
};
let sut: EntryFormatter = (&format).into();
let result = sut.apply(input);
let expected = vec![
Entry {
directory: PathBuf::from("/home/user"),
file: PathBuf::from("/tmp/source1.c"),
arguments: vec_of_strings!["clang", "-o", "./source1.o", "/tmp/source1.c"],
output: None,
},
Entry {
directory: PathBuf::from("/home/user"),
file: PathBuf::from("../source2.c"),
arguments: vec_of_strings!["clang", "-Wall", "../source2.c"],
output: None,
},
];
assert_eq!(expected, result);
}
#[test]
fn test_multiple_sources_compilation_with_abs_paths() {
let input = compiler_call_with_multiple_passes();
let format = config::Format {
command_as_array: true,
drop_output_field: true,
paths_as: config::PathFormat::Absolute,
};
let sut: EntryFormatter = (&format).into();
let result = sut.apply(input);
let expected = vec![
Entry {
directory: PathBuf::from("/home/user"),
file: PathBuf::from("/tmp/source1.c"),
arguments: vec_of_strings!["clang", "-o", "./source1.o", "/tmp/source1.c"],
output: None,
},
Entry {
directory: PathBuf::from("/home/user"),
file: PathBuf::from("/home/source2.c"),
arguments: vec_of_strings!["clang", "-Wall", "../source2.c"],
output: None,
},
];
assert_eq!(expected, result);
}
fn compiler_call_with_multiple_passes() -> semantic::CompilerCall {
semantic::CompilerCall {
compiler: PathBuf::from("clang"),
working_dir: PathBuf::from("/home/user"),
passes: vec![
semantic::CompilerPass::Preprocess,
semantic::CompilerPass::Compile {
source: PathBuf::from("/tmp/source1.c"),
output: Some(PathBuf::from("./source1.o")),
flags: vec_of_strings![],
},
semantic::CompilerPass::Compile {
source: PathBuf::from("../source2.c"),
output: None,
flags: vec_of_strings!["-Wall"],
},
],
}
}
}
rizsotto-Bear-14c2e01/rust/bear/src/output/mod.rs 0000664 0000000 0000000 00000000664 14767742337 0022013 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use super::semantic;
use anyhow::Result;
pub mod clang;
pub mod filter;
pub mod formatter;
/// The output writer trait is responsible for writing output file.
pub(crate) trait OutputWriter {
/// Running the writer means to consume the compiler calls
/// and write the entries to the output file.
fn run(&self, _: impl Iterator- ) -> Result<()>;
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/ 0000775 0000000 0000000 00000000000 14767742337 0021123 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/ 0000775 0000000 0000000 00000000000 14767742337 0023651 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/combinators.rs 0000664 0000000 0000000 00000007521 14767742337 0026544 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use super::super::{CompilerCall, Execution, Interpreter, Recognition};
/// Represents a set of interpreters, where any of them can recognize the semantic.
/// The evaluation is done in the order of the interpreters. The first one which
/// recognizes the semantic will be returned as result.
pub(super) struct Any {
interpreters: Vec>,
}
impl Any {
pub(super) fn new(tools: Vec>) -> Self {
Self {
interpreters: tools,
}
}
}
impl Interpreter for Any {
fn recognize(&self, x: &Execution) -> Recognition {
for tool in &self.interpreters {
match tool.recognize(x) {
Recognition::Unknown => continue,
result => return result,
}
}
Recognition::Unknown
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use std::path::PathBuf;
use super::super::super::CompilerCall;
use super::*;
#[test]
fn test_any_when_no_match() {
let sut = Any {
interpreters: vec![
Box::new(MockTool::NotRecognize),
Box::new(MockTool::NotRecognize),
Box::new(MockTool::NotRecognize),
],
};
let input = any_execution();
match sut.recognize(&input) {
Recognition::Unknown => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_any_when_success() {
let sut = Any {
interpreters: vec![
Box::new(MockTool::NotRecognize),
Box::new(MockTool::Recognize),
Box::new(MockTool::NotRecognize),
],
};
let input = any_execution();
match sut.recognize(&input) {
Recognition::Success(_) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_any_when_ignored() {
let sut = Any {
interpreters: vec![
Box::new(MockTool::NotRecognize),
Box::new(MockTool::RecognizeIgnored),
Box::new(MockTool::Recognize),
],
};
let input = any_execution();
match sut.recognize(&input) {
Recognition::Ignored => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_any_when_match_fails() {
let sut = Any {
interpreters: vec![
Box::new(MockTool::NotRecognize),
Box::new(MockTool::RecognizeFailed),
Box::new(MockTool::Recognize),
Box::new(MockTool::NotRecognize),
],
};
let input = any_execution();
match sut.recognize(&input) {
Recognition::Error(_) => assert!(true),
_ => assert!(false),
}
}
enum MockTool {
Recognize,
RecognizeIgnored,
RecognizeFailed,
NotRecognize,
}
impl Interpreter for MockTool {
fn recognize(&self, _: &Execution) -> Recognition {
match self {
MockTool::Recognize => Recognition::Success(any_compiler_call()),
MockTool::RecognizeIgnored => Recognition::Ignored,
MockTool::RecognizeFailed => Recognition::Error(String::from("problem")),
MockTool::NotRecognize => Recognition::Unknown,
}
}
}
fn any_execution() -> Execution {
Execution {
executable: PathBuf::new(),
arguments: vec![],
working_dir: PathBuf::new(),
environment: HashMap::new(),
}
}
fn any_compiler_call() -> CompilerCall {
CompilerCall {
compiler: PathBuf::new(),
working_dir: PathBuf::new(),
passes: vec![],
}
}
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/gcc.rs 0000664 0000000 0000000 00000014625 14767742337 0024763 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use nom::branch::alt;
use nom::multi::many1;
use nom::sequence::preceded;
use super::super::{CompilerCall, Execution, Interpreter, Recognition};
use internal::Argument;
pub(super) struct Gcc {}
impl Gcc {
pub(super) fn new() -> Self {
Gcc {}
}
}
impl Interpreter for Gcc {
fn recognize(&self, execution: &Execution) -> Recognition {
let mut parser = preceded(
internal::compiler,
many1(alt((internal::flag, internal::source))),
);
match parser(execution.arguments.as_slice()) {
Ok(result) => {
// TODO: append flags from environment
let flags = result.1;
let passes = Argument::passes(flags.as_slice());
Recognition::Success(CompilerCall {
compiler: execution.executable.clone(),
working_dir: execution.working_dir.clone(),
passes,
})
}
Err(error) => {
log::debug!("Gcc failed to parse it: {error}.");
Recognition::Unknown
}
}
}
}
mod internal {
use nom::{error::ErrorKind, IResult};
use regex::Regex;
use std::path::PathBuf;
use super::super::super::CompilerPass;
use super::super::matchers::source::looks_like_a_source_file;
#[derive(Debug, PartialEq)]
enum Language {
C,
Cpp,
ObjectiveC,
ObjectiveCpp,
Ada,
Fortran,
Go,
D,
Assembler,
Other,
}
#[derive(Debug, PartialEq)]
enum Pass {
Preprocessor,
Compiler,
Linker,
}
#[derive(Debug, PartialEq)]
enum Meaning {
Compiler,
ControlKindOfOutput { stop_before: Option },
ControlLanguage(Language),
ControlPass(Pass),
Diagnostic,
Debug,
Optimize,
Instrumentation,
DirectorySearch(Option),
Developer,
Input(Pass),
Output,
}
/// Compiler flags are varies the number of arguments, but means one thing.
pub(super) struct Argument<'a> {
arguments: &'a [String],
meaning: Meaning,
}
impl Argument<'_> {
pub(super) fn passes(flags: &[Argument]) -> Vec {
let mut pass: Pass = Pass::Linker;
let mut inputs: Vec = vec![];
let mut output: Option = None;
let mut args: Vec = vec![];
for flag in flags {
match flag.meaning {
Meaning::ControlKindOfOutput {
stop_before: Some(Pass::Compiler),
} => {
pass = Pass::Preprocessor;
args.extend(flag.arguments.iter().map(String::to_owned));
}
Meaning::ControlKindOfOutput {
stop_before: Some(Pass::Linker),
} => {
pass = Pass::Compiler;
args.extend(flag.arguments.iter().map(String::to_owned));
}
Meaning::ControlKindOfOutput { .. }
| Meaning::ControlLanguage(_)
| Meaning::ControlPass(Pass::Preprocessor)
| Meaning::ControlPass(Pass::Compiler)
| Meaning::Diagnostic
| Meaning::Debug
| Meaning::Optimize
| Meaning::Instrumentation
| Meaning::DirectorySearch(None) => {
args.extend(flag.arguments.iter().map(String::to_owned));
}
Meaning::Input(_) => {
assert_eq!(flag.arguments.len(), 1);
inputs.push(flag.arguments[0].clone())
}
Meaning::Output => {
assert_eq!(flag.arguments.len(), 1);
output = Some(flag.arguments[0].clone())
}
_ => {}
}
}
match pass {
Pass::Preprocessor if inputs.is_empty() => {
vec![]
}
Pass::Preprocessor => {
vec![CompilerPass::Preprocess]
}
Pass::Compiler | Pass::Linker => inputs
.into_iter()
.map(|source| CompilerPass::Compile {
source: PathBuf::from(source),
output: output.as_ref().map(PathBuf::from),
flags: args.clone(),
})
.collect(),
}
}
}
pub(super) fn compiler(i: &[String]) -> IResult<&[String], Argument> {
let candidate = &i[0];
if COMPILER_REGEX.is_match(candidate) {
const MEANING: Meaning = Meaning::Compiler;
Ok((
&i[1..],
Argument {
arguments: &i[..0],
meaning: MEANING,
},
))
} else {
// Declare it as a non-recoverable error, so argument processing will stop after this.
Err(nom::Err::Failure(nom::error::Error::new(i, ErrorKind::Tag)))
}
}
pub(super) fn source(i: &[String]) -> IResult<&[String], Argument> {
let candidate = &i[0];
if looks_like_a_source_file(candidate.as_str()) {
const MEANING: Meaning = Meaning::Input(Pass::Preprocessor);
Ok((
&i[1..],
Argument {
arguments: &i[..0],
meaning: MEANING,
},
))
} else {
Err(nom::Err::Error(nom::error::Error::new(i, ErrorKind::Tag)))
}
}
pub(super) fn flag(_i: &[String]) -> IResult<&[String], Argument> {
todo!()
}
static COMPILER_REGEX: std::sync::LazyLock = std::sync::LazyLock::new(|| {
// - cc
// - c++
// - cxx
// - CC
// - mcc, gcc, m++, g++, gfortran, fortran
// - with prefixes like: arm-none-eabi-
// - with postfixes like: -7.0 or 6.4.0
Regex::new(
r"(^(cc|c\+\+|cxx|CC|(([^-]*-)*([mg](cc|\+\+)|[g]?fortran)(-?\d+(\.\d+){0,2})?))$)",
)
.unwrap()
});
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/generic.rs 0000664 0000000 0000000 00000010070 14767742337 0025631 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use std::collections::HashSet;
use std::path::PathBuf;
use std::vec;
use super::super::{CompilerCall, CompilerPass, Execution, Interpreter, Recognition};
use super::matchers::source::looks_like_a_source_file;
/// A tool to recognize a compiler by executable name.
pub(super) struct Generic {
executables: HashSet,
}
impl Generic {
pub(super) fn from(compilers: &[PathBuf]) -> Self {
let executables = compilers.iter().cloned().collect();
Self { executables }
}
}
impl Interpreter for Generic {
/// This tool is a naive implementation only considering:
/// - the executable name,
/// - one of the arguments is a source file,
/// - the rest of the arguments are flags.
fn recognize(&self, x: &Execution) -> Recognition {
if self.executables.contains(&x.executable) {
let mut flags = vec![];
let mut sources = vec![];
// find sources and filter out requested flags.
for argument in x.arguments.iter().skip(1) {
if looks_like_a_source_file(argument.as_str()) {
sources.push(PathBuf::from(argument));
} else {
flags.push(argument.clone());
}
}
if sources.is_empty() {
Recognition::Error(String::from("source file is not found"))
} else {
Recognition::Success(CompilerCall {
compiler: x.executable.clone(),
working_dir: x.working_dir.clone(),
passes: sources
.iter()
.map(|source| CompilerPass::Compile {
source: source.clone(),
output: None,
flags: flags.clone(),
})
.collect(),
})
}
} else {
Recognition::Unknown
}
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use crate::{vec_of_pathbuf, vec_of_strings};
use super::*;
#[test]
fn test_matching() {
let input = Execution {
executable: PathBuf::from("/usr/bin/something"),
arguments: vec_of_strings![
"something",
"-Dthis=that",
"-I.",
"source.c",
"-o",
"source.c.o"
],
working_dir: PathBuf::from("/home/user"),
environment: HashMap::new(),
};
let expected = CompilerCall {
compiler: PathBuf::from("/usr/bin/something"),
working_dir: PathBuf::from("/home/user"),
passes: vec![CompilerPass::Compile {
flags: vec_of_strings!["-Dthis=that", "-I.", "-o", "source.c.o"],
source: PathBuf::from("source.c"),
output: None,
}],
};
assert_eq!(Recognition::Success(expected), SUT.recognize(&input));
}
#[test]
fn test_matching_without_sources() {
let input = Execution {
executable: PathBuf::from("/usr/bin/something"),
arguments: vec_of_strings!["something", "--help"],
working_dir: PathBuf::from("/home/user"),
environment: HashMap::new(),
};
assert_eq!(
Recognition::Error(String::from("source file is not found")),
SUT.recognize(&input)
);
}
#[test]
fn test_not_matching() {
let input = Execution {
executable: PathBuf::from("/usr/bin/cc"),
arguments: vec_of_strings!["cc", "-Dthis=that", "-I.", "source.c", "-o", "source.c.o"],
working_dir: PathBuf::from("/home/user"),
environment: HashMap::new(),
};
assert_eq!(Recognition::Unknown, SUT.recognize(&input));
}
static SUT: std::sync::LazyLock = std::sync::LazyLock::new(|| Generic {
executables: vec_of_pathbuf!["/usr/bin/something"].into_iter().collect(),
});
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/ignore.rs 0000664 0000000 0000000 00000010500 14767742337 0025476 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use std::collections::HashSet;
use std::path::PathBuf;
use super::super::{CompilerCall, Execution, Interpreter, Recognition};
/// A tool to ignore a command execution by executable name.
pub(super) struct IgnoreByPath {
executables: HashSet,
}
impl IgnoreByPath {
pub(super) fn new() -> Self {
let executables = COREUTILS_FILES.iter().map(PathBuf::from).collect();
Self { executables }
}
pub(super) fn from(compilers: &[PathBuf]) -> Self {
let executables = compilers.iter().cloned().collect();
Self { executables }
}
}
impl Default for IgnoreByPath {
fn default() -> Self {
Self::new()
}
}
/// A tool to ignore a command execution by arguments.
impl Interpreter for IgnoreByPath {
fn recognize(&self, execution: &Execution) -> Recognition {
if self.executables.contains(&execution.executable) {
Recognition::Ignored
} else {
Recognition::Unknown
}
}
}
static COREUTILS_FILES: [&str; 106] = [
"/usr/bin/[",
"/usr/bin/arch",
"/usr/bin/b2sum",
"/usr/bin/base32",
"/usr/bin/base64",
"/usr/bin/basename",
"/usr/bin/basenc",
"/usr/bin/cat",
"/usr/bin/chcon",
"/usr/bin/chgrp",
"/usr/bin/chmod",
"/usr/bin/chown",
"/usr/bin/cksum",
"/usr/bin/comm",
"/usr/bin/cp",
"/usr/bin/csplit",
"/usr/bin/cut",
"/usr/bin/date",
"/usr/bin/dd",
"/usr/bin/df",
"/usr/bin/dir",
"/usr/bin/dircolors",
"/usr/bin/dirname",
"/usr/bin/du",
"/usr/bin/echo",
"/usr/bin/env",
"/usr/bin/expand",
"/usr/bin/expr",
"/usr/bin/factor",
"/usr/bin/false",
"/usr/bin/fmt",
"/usr/bin/fold",
"/usr/bin/groups",
"/usr/bin/head",
"/usr/bin/hostid",
"/usr/bin/id",
"/usr/bin/install",
"/usr/bin/join",
"/usr/bin/link",
"/usr/bin/ln",
"/usr/bin/logname",
"/usr/bin/ls",
"/usr/bin/md5sum",
"/usr/bin/mkdir",
"/usr/bin/mkfifo",
"/usr/bin/mknod",
"/usr/bin/mktemp",
"/usr/bin/mv",
"/usr/bin/nice",
"/usr/bin/nl",
"/usr/bin/nohup",
"/usr/bin/nproc",
"/usr/bin/numfmt",
"/usr/bin/od",
"/usr/bin/paste",
"/usr/bin/pathchk",
"/usr/bin/pinky",
"/usr/bin/pr",
"/usr/bin/printenv",
"/usr/bin/printf",
"/usr/bin/ptx",
"/usr/bin/pwd",
"/usr/bin/readlink",
"/usr/bin/realpath",
"/usr/bin/rm",
"/usr/bin/rmdir",
"/usr/bin/runcon",
"/usr/bin/seq",
"/usr/bin/sha1sum",
"/usr/bin/sha224sum",
"/usr/bin/sha256sum",
"/usr/bin/sha384sum",
"/usr/bin/sha512sum",
"/usr/bin/shred",
"/usr/bin/shuf",
"/usr/bin/sleep",
"/usr/bin/sort",
"/usr/bin/split",
"/usr/bin/stat",
"/usr/bin/stdbuf",
"/usr/bin/stty",
"/usr/bin/sum",
"/usr/bin/sync",
"/usr/bin/tac",
"/usr/bin/tail",
"/usr/bin/tee",
"/usr/bin/test",
"/usr/bin/timeout",
"/usr/bin/touch",
"/usr/bin/tr",
"/usr/bin/true",
"/usr/bin/truncate",
"/usr/bin/tsort",
"/usr/bin/tty",
"/usr/bin/uname",
"/usr/bin/unexpand",
"/usr/bin/uniq",
"/usr/bin/unlink",
"/usr/bin/users",
"/usr/bin/vdir",
"/usr/bin/wc",
"/usr/bin/who",
"/usr/bin/whoami",
"/usr/bin/yes",
"/usr/bin/make",
"/usr/bin/gmake",
];
#[cfg(test)]
mod test {
use std::collections::HashMap;
use std::path::PathBuf;
use crate::vec_of_strings;
use super::*;
#[test]
fn test_executions_are_ignored_by_executable_name() {
let input = Execution {
executable: PathBuf::from("/usr/bin/ls"),
arguments: vec_of_strings!["ls", "/home/user/build"],
working_dir: PathBuf::from("/home/user"),
environment: HashMap::new(),
};
let sut = IgnoreByPath::new();
assert_eq!(Recognition::Ignored, sut.recognize(&input))
}
#[test]
fn test_not_known_executables_are_not_recognized() {
let input = Execution {
executable: PathBuf::from("/usr/bin/bear"),
arguments: vec_of_strings!["bear", "--", "make"],
working_dir: PathBuf::from("/home/user"),
environment: HashMap::new(),
};
let sut = IgnoreByPath::new();
assert_eq!(Recognition::Unknown, sut.recognize(&input))
}
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/matchers/ 0000775 0000000 0000000 00000000000 14767742337 0025457 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/matchers/mod.rs 0000664 0000000 0000000 00000000105 14767742337 0026600 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
pub(super) mod source;
rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/matchers/source.rs 0000664 0000000 0000000 00000004230 14767742337 0027324 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use std::collections::HashSet;
#[cfg(target_family = "unix")]
pub fn looks_like_a_source_file(argument: &str) -> bool {
// not a command line flag
if argument.starts_with('-') {
return false;
}
if let Some((_, extension)) = argument.rsplit_once('.') {
return EXTENSIONS.contains(extension);
}
false
}
#[cfg(target_family = "windows")]
pub fn looks_like_a_source_file(argument: &str) -> bool {
// not a command line flag
if argument.starts_with('/') {
return false;
}
if let Some((_, extension)) = argument.rsplit_once('.') {
return EXTENSIONS.contains(extension);
}
false
}
#[rustfmt::skip]
static EXTENSIONS: std::sync::LazyLock> = std::sync::LazyLock::new(|| {
HashSet::from([
// header files
"h", "hh", "H", "hp", "hxx", "hpp", "HPP", "h++", "tcc",
// C
"c", "C",
// C++
"cc", "CC", "c++", "C++", "cxx", "cpp", "cp",
// CUDA
"cu",
// ObjectiveC
"m", "mi", "mm", "M", "mii",
// Preprocessed
"i", "ii",
// Assembly
"s", "S", "sx", "asm",
// Fortran
"f", "for", "ftn",
"F", "FOR", "fpp", "FPP", "FTN",
"f90", "f95", "f03", "f08",
"F90", "F95", "F03", "F08",
// go
"go",
// brig
"brig",
// D
"d", "di", "dd",
// Ada
"ads", "abd",
])
});
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_filenames() {
assert!(looks_like_a_source_file("source.c"));
assert!(looks_like_a_source_file("source.cpp"));
assert!(looks_like_a_source_file("source.cxx"));
assert!(looks_like_a_source_file("source.cc"));
assert!(looks_like_a_source_file("source.h"));
assert!(looks_like_a_source_file("source.hpp"));
assert!(!looks_like_a_source_file("gcc"));
assert!(!looks_like_a_source_file("clang"));
assert!(!looks_like_a_source_file("-o"));
assert!(!looks_like_a_source_file("-Wall"));
assert!(!looks_like_a_source_file("/o"));
}
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/mod.rs 0000664 0000000 0000000 00000010656 14767742337 0025006 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
use super::interpreters::combinators::Any;
use super::interpreters::generic::Generic;
use super::interpreters::ignore::IgnoreByPath;
use super::Interpreter;
use crate::config;
use std::path::PathBuf;
mod combinators;
mod gcc;
mod generic;
mod ignore;
mod matchers;
/// Creates an interpreter to recognize the compiler calls.
///
/// Using the configuration we can define which compilers to include and exclude.
/// Also read the environment variables to detect the compiler to include (and
/// make sure those are not excluded either).
// TODO: Use the CC or CXX environment variables to detect the compiler to include.
// Use the CC or CXX environment variables and make sure those are not excluded.
// Make sure the environment variables are passed to the method.
// TODO: Take environment variables as input.
pub fn create_interpreter<'a>(config: &config::Main) -> impl Interpreter + 'a {
let compilers_to_include = match &config.intercept {
config::Intercept::Wrapper { executables, .. } => executables.clone(),
_ => vec![],
};
let compilers_to_exclude = match &config.output {
config::Output::Clang { compilers, .. } => compilers
.iter()
.filter(|compiler| compiler.ignore == config::IgnoreOrConsider::Always)
.map(|compiler| compiler.path.clone())
.collect(),
_ => vec![],
};
let mut interpreters: Vec> = vec![
// ignore executables which are not compilers,
Box::new(IgnoreByPath::default()),
// recognize default compiler
Box::new(Generic::from(&[PathBuf::from("/usr/bin/cc")])),
];
if !compilers_to_include.is_empty() {
let tool = Generic::from(&compilers_to_include);
interpreters.push(Box::new(tool));
}
if !compilers_to_exclude.is_empty() {
let tool = IgnoreByPath::from(&compilers_to_exclude);
interpreters.insert(0, Box::new(tool));
}
Any::new(interpreters)
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use std::path::PathBuf;
use super::super::{CompilerCall, Execution, Recognition};
use super::*;
use crate::config;
use crate::config::{DuplicateFilter, Format, SourceFilter};
use crate::{vec_of_pathbuf, vec_of_strings};
fn any_execution() -> Execution {
Execution {
executable: PathBuf::from("/usr/bin/cc"),
arguments: vec_of_strings!["cc", "-c", "-Wall", "main.c"],
environment: HashMap::new(),
working_dir: PathBuf::from("/home/user"),
}
}
#[test]
fn test_create_interpreter_with_default_config() {
let config = config::Main::default();
let interpreter = create_interpreter(&config);
let input = any_execution();
match interpreter.recognize(&input) {
Recognition::Success(CompilerCall { .. }) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_create_interpreter_with_compilers_to_include() {
let config = config::Main {
intercept: config::Intercept::Wrapper {
executables: vec_of_pathbuf!["/usr/bin/cc"],
path: PathBuf::from("/usr/libexec/bear"),
directory: PathBuf::from("/tmp"),
},
..Default::default()
};
let interpreter = create_interpreter(&config);
let input = any_execution();
match interpreter.recognize(&input) {
Recognition::Success(CompilerCall { .. }) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn test_create_interpreter_with_compilers_to_exclude() {
let config = config::Main {
output: config::Output::Clang {
compilers: vec![config::Compiler {
path: PathBuf::from("/usr/bin/cc"),
ignore: config::IgnoreOrConsider::Always,
arguments: config::Arguments::default(),
}],
sources: SourceFilter::default(),
duplicates: DuplicateFilter::default(),
format: Format::default(),
},
..Default::default()
};
let interpreter = create_interpreter(&config);
let input = any_execution();
match interpreter.recognize(&input) {
Recognition::Ignored => assert!(true),
_ => assert!(false),
}
}
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/mod.rs 0000664 0000000 0000000 00000006267 14767742337 0022263 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
//! This module is defining the semantic of executed commands.
//!
//! The semantic identifies the intent of the execution. It not only
//! recognizes the compiler calls, but also identifies the compiler
//! passes that are executed.
//!
//! A compilation of a source file can be divided into multiple passes.
//! We are interested in the compiler passes, because those are the
//! ones that are relevant to build a JSON compilation database.
pub mod interpreters;
pub mod transformation;
use super::intercept::Execution;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use std::path::PathBuf;
/// Represents an executed command semantic.
#[derive(Debug, PartialEq, Serialize)]
pub struct CompilerCall {
pub compiler: PathBuf,
pub working_dir: PathBuf,
pub passes: Vec,
}
/// Represents a compiler call pass.
#[derive(Debug, PartialEq, Serialize)]
pub enum CompilerPass {
Preprocess,
Compile {
source: PathBuf,
output: Option,
flags: Vec,
},
}
/// Responsible to recognize the semantic of an executed command.
///
/// The implementation can be responsible for a single compiler,
/// a set of compilers, or a set of commands that are not compilers.
///
/// The benefit to recognize a non-compiler command, is to not
/// spend more time to try to recognize with other interpreters.
/// Or classify the recognition as ignored to not be further processed
/// later on.
pub trait Interpreter: Send {
fn recognize(&self, _: &Execution) -> Recognition;
}
/// Represents a semantic recognition result.
///
/// The unknown recognition is used when the interpreter is not
/// able to recognize the command. This can signal the search process
/// to continue with the next interpreter.
#[derive(Debug, PartialEq)]
pub enum Recognition {
/// The command was recognized and the semantic was identified.
Success(T),
/// The command was recognized, but the semantic was ignored.
Ignored,
/// The command was recognized, but the semantic was broken.
Error(String),
/// The command was not recognized.
Unknown,
}
impl IntoIterator for Recognition {
type Item = T;
type IntoIter = std::option::IntoIter;
fn into_iter(self) -> Self::IntoIter {
match self {
Recognition::Success(value) => Some(value).into_iter(),
_ => None.into_iter(),
}
}
}
/// Responsible to transform the semantic of an executed command.
///
/// It conditionally removes compiler calls based on compiler names or flags.
/// It can also alter the compiler flags of the compiler calls. The actions
/// are defined in the configuration this module is given.
pub trait Transform: Send {
fn apply(&self, _: CompilerCall) -> Option;
}
/// Serialize compiler calls into a JSON array.
pub fn serialize(
writer: impl std::io::Write,
entries: impl Iterator
- + Sized,
) -> anyhow::Result<()> {
let mut ser = serde_json::Serializer::pretty(writer);
let mut seq = ser.serialize_seq(None)?;
for entry in entries {
seq.serialize_element(&entry)?;
}
seq.end()?;
Ok(())
}
rizsotto-Bear-14c2e01/rust/bear/src/semantic/transformation.rs 0000664 0000000 0000000 00000023073 14767742337 0024544 0 ustar 00root root 0000000 0000000 // SPDX-License-Identifier: GPL-3.0-or-later
//! Responsible for transforming the compiler calls.
//!
//! It conditionally removes compiler calls based on compiler names or flags.
//! It can also alter the compiler flags of the compiler calls. The actions
//! are defined in the configuration this module is given.
use crate::{config, semantic};
use std::collections::HashMap;
use std::path::PathBuf;
/// Transformation contains rearranged information from the configuration.
///
/// The configuration is a list of instruction on how to transform the compiler call.
/// The transformation group the instructions by the compiler path, so it can be
/// applied to the compiler call when it matches the path.
#[derive(Debug, PartialEq)]
pub struct Transformation {
compilers: HashMap>,
}
impl From<&config::Output> for Transformation {
fn from(config: &config::Output) -> Self {
match config {
config::Output::Clang { compilers, .. } => compilers.as_slice().into(),
config::Output::Semantic { .. } => Transformation::new(),
}
}
}
impl From<&[config::Compiler]> for Transformation {
fn from(config: &[config::Compiler]) -> Self {
let mut compilers = HashMap::new();
for compiler in config {
compilers
.entry(compiler.path.clone())
.or_insert_with(Vec::new)
.push(compiler.clone());
}
Transformation { compilers }
}
}
impl semantic::Transform for Transformation {
fn apply(&self, input: semantic::CompilerCall) -> Option {
if let Some(configs) = self.compilers.get(&input.compiler) {
Self::apply_when_not_empty(configs.as_slice(), input)
} else {
Some(input)
}
}
}
impl Transformation {
fn new() -> Self {
Transformation {
compilers: HashMap::new(),
}
}
/// Apply the transformation to the compiler call.
///
/// Multiple configurations can be applied to the same compiler call.
/// And depending on the instruction from the configuration, the compiler call
/// can be ignored, modified, or left unchanged. The conditional ignore will
/// check if the compiler call matches the flags defined in the configuration.
fn apply_when_not_empty(
configs: &[config::Compiler],
input: semantic::CompilerCall,
) -> Option {
let mut current_input = Some(input);
for config in configs {
current_input = match config {
config::Compiler {
ignore: config::IgnoreOrConsider::Always,
..
} => None,
config::Compiler {
ignore: config::IgnoreOrConsider::Conditional,
arguments,
..
} => current_input.filter(|input| !Self::match_condition(arguments, &input.passes)),
config::Compiler {
ignore: config::IgnoreOrConsider::Never,
arguments,
..
} => current_input.map(|input| semantic::CompilerCall {
compiler: input.compiler.clone(),
working_dir: input.working_dir.clone(),
passes: Transformation::apply_argument_changes(
arguments,
input.passes.as_slice(),
),
}),
};
if current_input.is_none() {
break;
}
}
current_input
}
/// Check if the compiler call matches the condition defined in the configuration.
///
/// Any compiler pass that matches the flags defined in the configuration will cause
/// the whole compiler call to be ignored.
fn match_condition(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
let match_flags = arguments.match_.as_slice();
passes.iter().any(|pass| match pass {
semantic::CompilerPass::Compile { flags, .. } => {
flags.iter().any(|flag| match_flags.contains(flag))
}
_ => false,
})
}
/// Apply the changes defined in the configuration to the compiler call.
///
/// The changes can be to remove or add flags to the compiler call.
/// Only the flags will be changed, but applies to all compiler passes.
fn apply_argument_changes(
arguments: &config::Arguments,
passes: &[semantic::CompilerPass],
) -> Vec {
let arguments_to_remove = arguments.remove.as_slice();
let arguments_to_add = arguments.add.as_slice();
let mut new_passes = Vec::with_capacity(passes.len());
for pass in passes {
match pass {
semantic::CompilerPass::Compile {
source,
output,
flags,
} => {
let mut new_flags = flags.clone();
new_flags.retain(|flag| !arguments_to_remove.contains(flag));
new_flags.extend(arguments_to_add.iter().cloned());
new_passes.push(semantic::CompilerPass::Compile {
source: source.clone(),
output: output.clone(),
flags: new_flags,
});
}
semantic::CompilerPass::Preprocess => {
new_passes.push(semantic::CompilerPass::Preprocess)
}
}
}
new_passes
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{Arguments, Compiler, IgnoreOrConsider};
use crate::semantic::{CompilerCall, CompilerPass, Transform};
use std::path::PathBuf;
#[test]
fn test_apply_no_filter() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
let sut = Transformation::from(&config::Output::Semantic {});
let result = sut.apply(input);
let expected = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
assert_eq!(result, Some(expected));
}
#[test]
fn test_apply_filter_match() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("cc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
let sut: Transformation = vec![Compiler {
path: std::path::PathBuf::from("cc"),
ignore: IgnoreOrConsider::Always,
arguments: Arguments::default(),
}]
.as_slice()
.into();
let result = sut.apply(input);
assert!(result.is_none());
}
#[test]
fn test_apply_conditional_match() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into(), "-Wall".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
let sut: Transformation = vec![Compiler {
path: std::path::PathBuf::from("gcc"),
ignore: IgnoreOrConsider::Conditional,
arguments: Arguments {
match_: vec!["-O2".into()],
..Arguments::default()
},
}]
.as_slice()
.into();
let result = sut.apply(input);
assert!(result.is_none());
}
#[test]
fn test_apply_ignore_never_modify_arguments() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
let sut: Transformation = vec![Compiler {
path: std::path::PathBuf::from("gcc"),
ignore: IgnoreOrConsider::Never,
arguments: Arguments {
add: vec!["-Wall".into()],
remove: vec!["-O2".into()],
..Arguments::default()
},
}]
.as_slice()
.into();
let result = sut.apply(input);
let expected = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-Wall".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
assert_eq!(result, Some(expected));
}
}
rizsotto-Bear-14c2e01/source/ 0000775 0000000 0000000 00000000000 14767742337 0016123 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/.clang-format 0000664 0000000 0000000 00000007120 14767742337 0020476 0 ustar 00root root 0000000 0000000 ---
Language: Cpp
# BasedOnStyle: WebKit
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: "Empty"
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: "false"
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: All
BreakBeforeBraces: WebKit
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: "c++17"
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
...
rizsotto-Bear-14c2e01/source/CMakeLists.txt 0000664 0000000 0000000 00000006062 14767742337 0020667 0 ustar 00root root 0000000 0000000 cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
cmake_policy(VERSION 3.12)
project(BearSource
VERSION ${CMAKE_PROJECT_VERSION}
LANGUAGES C CXX
)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSPDLOG_NO_EXCEPTIONS")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGOOGLE_PROTOBUF_NO_RTTI")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
if (ENABLE_UNIT_TESTS)
find_program(MEMORYCHECK_COMMAND NAMES valgrind)
set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full")
include(CTest)
enable_testing()
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTest REQUIRED IMPORTED_TARGET gtest gtest_main gmock)
endif ()
find_package(Threads REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(gRPC REQUIRED IMPORTED_TARGET protobuf grpc++)
if (fmt_VERSION_MAJOR GREATER_EQUAL 9)
set(FMT_NEEDS_OSTREAM_FORMATTER 1)
set(HAVE_FMT_STD_H 1) # FIXME: this should be done with `check_include_file`
endif ()
if (UNIX AND NOT APPLE)
set(SUPPORT_PRELOAD 1)
endif()
if (ENABLE_MULTILIB)
set(SUPPORT_MULTILIB 1)
endif()
include(CheckIncludeFile)
check_include_file(spawn.h HAVE_SPAWN_H)
check_include_file(unistd.h HAVE_UNISTD_H)
check_include_file(dlfcn.h HAVE_DLFCN_H)
check_include_file(errno.h HAVE_ERRNO_H)
check_include_file(sys/utsname.h HAVE_SYS_UTSNAME_H)
check_include_file(sys/wait.h HAVE_SYS_WAIT_H)
check_include_file(sys/time.h HAVE_SYS_TIME_H)
check_include_file(sys/stat.h HAVE_SYS_STAT_H)
check_include_file(gnu/lib-names.h HAVE_GNU_LIB_NAMES_H)
#check_include_file(fmt/std.h HAVE_FMT_STD_H)
include(CheckSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS})
set(CMAKE_REQUIRED_FLAGS -D_GNU_SOURCE)
check_symbol_exists(_NSGetEnviron "crt_externs.h" HAVE_NSGETENVIRON)
check_symbol_exists(dlopen "dlfcn.h" HAVE_DLOPEN)
check_symbol_exists(dlsym "dlfcn.h" HAVE_DLSYM)
check_symbol_exists(dlerror "dlfcn.h" HAVE_DLERROR)
check_symbol_exists(dlclose "dlfcn.h" HAVE_DLCLOSE)
check_symbol_exists(RTLD_NEXT "dlfcn.h" HAVE_RTLD_NEXT)
check_symbol_exists(EACCES "errno.h" HAVE_EACCES)
check_symbol_exists(ENOENT "errno.h" HAVE_ENOENT)
check_symbol_exists(strerror_r "string.h" HAVE_STRERROR_R)
check_symbol_exists(environ "unistd.h" HAVE_ENVIRON)
check_symbol_exists(uname "sys/utsname.h" HAVE_UNAME)
check_symbol_exists(confstr "unistd.h" HAVE_CONFSTR)
check_symbol_exists(_CS_PATH "unistd.h" HAVE_CS_PATH)
check_symbol_exists(_CS_GNU_LIBC_VERSION "unistd.h" HAVE_CS_GNU_LIBC_VERSION)
check_symbol_exists(_CS_GNU_LIBPTHREAD_VERSION "unistd.h" HAVE_CS_GNU_LIBPTHREAD_VERSION)
include(GNUInstallDirs) # The directory names are used in the config file
configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_subdirectory(libresult)
add_subdirectory(libflags)
add_subdirectory(libshell)
add_subdirectory(libsys)
add_subdirectory(libmain)
add_subdirectory(intercept)
add_subdirectory(citnames)
add_subdirectory(bear)
rizsotto-Bear-14c2e01/source/bear/ 0000775 0000000 0000000 00000000000 14767742337 0017034 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/bear/CMakeLists.txt 0000664 0000000 0000000 00000002236 14767742337 0021577 0 ustar 00root root 0000000 0000000 # Create a static library, which is used for unit tests and the final shared library.
add_library(bear_a OBJECT)
target_include_directories(bear_a PUBLIC source/ ../citnames/include/ ../intercept/include/)
target_sources(bear_a
PRIVATE
source/Application.cc
INTERFACE
$
)
target_link_libraries(bear_a PUBLIC
main_a
sys_a
flags_a
fmt::fmt
citnames_a
intercept_a
spdlog::spdlog)
# Create an executable from the sub projects.
add_executable(bear
main.cc
)
target_link_libraries(bear
bear_a)
include(GNUInstallDirs)
install(TARGETS bear
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# Markdown file is the source to the man file. Please modify that and generate
# the man file from it with pandoc.
#
# $ pandoc -s -t man bear.1.md -o bear.1
#
# This is not automated, because pandoc has big dependencies on different OS
# distributions and packaging would require to install those. Which might be
# too much effort to generate a single text file.
install(FILES man/bear.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
rizsotto-Bear-14c2e01/source/bear/main.cc 0000664 0000000 0000000 00000001676 14767742337 0020301 0 ustar 00root root 0000000 0000000 /* Copyright (C) 2012-2024 by László Nagy
This file is part of Bear.
Bear is a tool to generate compilation database for clang tooling.
Bear is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Bear is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "source/Application.h"
#include "libmain/main.h"
int main(int argc, char *argv[], char *envp[]) {
return ps::main(argc, argv, envp);
}
rizsotto-Bear-14c2e01/source/bear/man/ 0000775 0000000 0000000 00000000000 14767742337 0017607 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/bear/man/bear.1 0000664 0000000 0000000 00000007605 14767742337 0020612 0 ustar 00root root 0000000 0000000 .\" Automatically generated by Pandoc 2.14.0.3
.\"
.TH "BEAR" "1" "Jan 02, 2023" "Bear User Manuals" ""
.hy
.SH NAME
.PP
Bear - a tool to generate compilation database for Clang tooling.
.SH SYNOPSIS
.PP
bear [\f[I]options\f[R]] -- [\f[I]build command\f[R]]
.SH DESCRIPTION
.PP
The JSON compilation database
is used in
Clang project to provide information how a single compilation unit was
processed.
When that is available then it is easy to re-run the compilation with
different programs.
.PP
Bear executes the original build command and intercept the command
executions issued by the build tool.
From the log of command executions it tries to identify the compiler
calls and creates the final compilation database.
.SH OPTIONS
.TP
--version
Print version number.
.TP
--help
Print help message.
.TP
--verbose
Enable verbose logging.
.TP
--output \f[I]file\f[R]
Specify output file.
(Default file name provided.) The output is a JSON compilation database.
.TP
--append
Use previously generated output file and append the new entries to it.
This way you can run Bear continuously during work, and it keeps the
compilation database up to date.
File deletion and addition are both considered.
But build process change (compiler flags change) might cause duplicate
entries.
.TP
--config \f[I]file\f[R]
Specify a configuration file.
The configuration file captures how the output should be formatted and
which entries it shall contain.
.TP
--force-preload
Force to use the dynamic linker method of \f[C]intercept\f[R] command.
.TP
--force-wrapper
Force to use the compiler wrapper method of \f[C]intercept\f[R] command.
.SH COMMANDS
.TP
\f[B]\f[CB]bear-intercept(1)\f[B]\f[R]
Intercepts events that happened during the execution of the build
command.
.TP
\f[B]\f[CB]bear-citnames(1)\f[B]\f[R]
Deduce the semantics of the commands captured by
\f[C]bear-intercept(1)\f[R].
.SH OUTPUT
.PP
The JSON compilation database definition changed over time.
The current version of Bear generates entries where:
.TP
\f[B]\f[CB]directory\f[B]\f[R]
has absolute path.
.TP
\f[B]\f[CB]file\f[B]\f[R]
has absolute path.
.TP
\f[B]\f[CB]output\f[B]\f[R]
has absolute path.
.TP
\f[B]\f[CB]arguments\f[B]\f[R]
used instead of \f[C]command\f[R] to avoid shell escaping problems.
(Configuration can force to emit the \f[C]command\f[R] field.) The
compiler as the first argument has absolute path.
Some non compilation related flags are filtered out from the final
output.
.SH CONFIG FILE
.PP
Read \f[C]bear-citnames(1)\f[R] man page for the content of this file.
\f[C]bear\f[R] is not reading the content of this file, but passing the
file name to \f[C]bear citnames\f[R] command.
.SH EXIT STATUS
.PP
The exit status of the program is the exit status of the build command.
Except when the program itself crashes, then it sets to non-zero.
.SH TROUBLESHOOTING
.PP
The potential problems you can face with are: the build with and without
Bear behaves differently or the output is empty.
.PP
The most common cause for empty outputs is that the build command did
not execute any commands.
The reason for that could be, because incremental builds not running the
compilers if everything is up-to-date.
Remember, Bear does not understand the build file (eg.: makefile), but
intercepts the executed commands.
.PP
The other common cause for empty output is that the build has a
\[lq]configure\[rq] step, which captures the compiler to build the
project.
In case of Bear is using the \f[I]wrapper\f[R] mode (read
\f[C]bear-intercept(1)\f[R] man page), it needs to run the configure
step with Bear too (and discard that output), before run the build with
Bear.
.PP
There could be many reasons for any of these failures.
It\[cq]s better to consult with the project wiki page for known
problems, before open a bug report.
.SH COPYRIGHT
.PP
Copyright (C) 2012-2024 by L\['a]szl\['o] Nagy
.SH AUTHORS
L\['a]szl\['o] Nagy.
rizsotto-Bear-14c2e01/source/bear/man/bear.1.md 0000664 0000000 0000000 00000007121 14767742337 0021202 0 ustar 00root root 0000000 0000000 % BEAR(1) Bear User Manuals
% László Nagy
% Jan 02, 2023
# NAME
Bear - a tool to generate compilation database for Clang tooling.
# SYNOPSIS
bear [*options*] \-\- [*build command*]
# DESCRIPTION
The JSON compilation database
is used in
Clang project to provide information how a single compilation unit
was processed. When that is available then it is easy to re-run the
compilation with different programs.
Bear executes the original build command and intercept the command
executions issued by the build tool. From the log of command executions
it tries to identify the compiler calls and creates the final
compilation database.
# OPTIONS
\--version
: Print version number.
\--help
: Print help message.
\--verbose
: Enable verbose logging.
\--output *file*
: Specify output file. (Default file name provided.) The output is
a JSON compilation database.
\--append
: Use previously generated output file and append the new entries to it.
This way you can run Bear continuously during work, and it keeps the
compilation database up to date. File deletion and addition are both
considered. But build process change (compiler flags change) might
cause duplicate entries.
\--config *file*
: Specify a configuration file. The configuration file captures how
the output should be formatted and which entries it shall contain.
\--force-preload
: Force to use the dynamic linker method of `intercept` command.
\--force-wrapper
: Force to use the compiler wrapper method of `intercept` command.
# COMMANDS
`bear-intercept(1)`
: Intercepts events that happened during the execution of the build
command.
`bear-citnames(1)`
: Deduce the semantics of the commands captured by `bear-intercept(1)`.
# OUTPUT
The JSON compilation database definition changed over time. The current
version of Bear generates entries where:
`directory`
: has absolute path.
`file`
: has absolute path.
`output`
: has absolute path.
`arguments`
: used instead of `command` to avoid shell escaping problems. (Configuration
can force to emit the `command` field.) The compiler as the first argument
has absolute path. Some non compilation related flags are filtered out from
the final output.
# CONFIG FILE
Read `bear-citnames(1)` man page for the content of this file. `bear` is not
reading the content of this file, but passing the file name to `bear citnames`
command.
# EXIT STATUS
The exit status of the program is the exit status of the build command.
Except when the program itself crashes, then it sets to non-zero.
# TROUBLESHOOTING
The potential problems you can face with are: the build with and without Bear
behaves differently or the output is empty.
The most common cause for empty outputs is that the build command did not
execute any commands. The reason for that could be, because incremental builds
not running the compilers if everything is up-to-date. Remember, Bear does not
understand the build file (eg.: makefile), but intercepts the executed
commands.
The other common cause for empty output is that the build has a "configure"
step, which captures the compiler to build the project. In case of Bear is
using the _wrapper_ mode (read `bear-intercept(1)` man page), it needs to
run the configure step with Bear too (and discard that output), before run
the build with Bear.
There could be many reasons for any of these failures. It's better to consult
with the project wiki page for known problems, before open a bug report.
# COPYRIGHT
Copyright (C) 2012-2024 by László Nagy
rizsotto-Bear-14c2e01/source/bear/source/ 0000775 0000000 0000000 00000000000 14767742337 0020334 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/bear/source/Application.cc 0000664 0000000 0000000 00000031216 14767742337 0023111 0 ustar 00root root 0000000 0000000 /* (C) 2012-2022 by László Nagy
This file is part of Bear.
Bear is a tool to generate compilation database for clang tooling.
Bear is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Bear is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "Application.h"
#include "citnames/citnames-forward.h"
#include "intercept/intercept-forward.h"
namespace {
constexpr std::optional ADVANCED_GROUP = {"advanced options"};
constexpr std::optional DEVELOPER_GROUP = {"developer options"};
rust::Result
prepare_intercept(const flags::Arguments &arguments, const sys::env::Vars &environment, const fs::path &output) {
auto program = arguments.as_string(cmd::bear::FLAG_BEAR);
auto command = arguments.as_string_list(cmd::intercept::FLAG_COMMAND);
auto library = arguments.as_string(cmd::intercept::FLAG_LIBRARY);
auto wrapper = arguments.as_string(cmd::intercept::FLAG_WRAPPER);
auto wrapper_dir = arguments.as_string(cmd::intercept::FLAG_WRAPPER_DIR);
auto verbose = arguments.as_bool(flags::VERBOSE).unwrap_or(false);
auto force_wrapper = arguments.as_bool(cmd::intercept::FLAG_FORCE_WRAPPER).unwrap_or(false);
auto force_preload = arguments.as_bool(cmd::intercept::FLAG_FORCE_PRELOAD).unwrap_or(false);
return rust::merge(program, command, rust::merge(library, wrapper, wrapper_dir))
.map(
[&environment, &output, &verbose, &force_wrapper, &force_preload](auto tuple) {
const auto&[program, command, pack] = tuple;
const auto&[library, wrapper, wrapper_dir] = pack;
auto builder = sys::Process::Builder(program)
.set_environment(environment)
.add_argument(program)
.add_argument("intercept")
.add_argument(cmd::intercept::FLAG_LIBRARY).add_argument(library)
.add_argument(cmd::intercept::FLAG_WRAPPER).add_argument(wrapper)
.add_argument(cmd::intercept::FLAG_WRAPPER_DIR).add_argument(wrapper_dir)
.add_argument(cmd::intercept::FLAG_OUTPUT).add_argument(output);
if (force_wrapper) {
builder.add_argument(cmd::intercept::FLAG_FORCE_WRAPPER);
}
if (force_preload) {
builder.add_argument(cmd::intercept::FLAG_FORCE_PRELOAD);
}
if (verbose) {
builder.add_argument(flags::VERBOSE);
}
builder.add_argument(cmd::intercept::FLAG_COMMAND)
.add_arguments(command.begin(), command.end());
return builder;
});
}
rust::Result
prepare_citnames(const flags::Arguments &arguments, const sys::env::Vars &environment, const fs::path &input) {
auto program = arguments.as_string(cmd::bear::FLAG_BEAR);
auto output = arguments.as_string(cmd::citnames::FLAG_OUTPUT);
auto config = arguments.as_string(cmd::citnames::FLAG_CONFIG);
auto append = arguments.as_bool(cmd::citnames::FLAG_APPEND).unwrap_or(false);
auto verbose = arguments.as_bool(flags::VERBOSE).unwrap_or(false);
return rust::merge(program, output)
.map([&environment, &input, &config, &append, &verbose](auto tuple) {
const auto&[program, output] = tuple;
auto builder = sys::Process::Builder(program)
.set_environment(environment)
.add_argument(program)
.add_argument("citnames")
.add_argument(cmd::citnames::FLAG_INPUT).add_argument(input)
.add_argument(cmd::citnames::FLAG_OUTPUT).add_argument(output)
// can run the file checks, because we are on the host.
.add_argument(cmd::citnames::FLAG_RUN_CHECKS);
if (append) {
builder.add_argument(cmd::citnames::FLAG_APPEND);
}
if (config.is_ok()) {
builder.add_argument(cmd::citnames::FLAG_CONFIG).add_argument(config.unwrap());
}
if (verbose) {
builder.add_argument(flags::VERBOSE);
}
return builder;
});
}
rust::Result execute(sys::Process::Builder builder, const std::string_view &name) {
return builder.spawn()
.and_then([](auto child) {
sys::SignalForwarder guard(child);
return child.wait();
})
.map([](auto status) {
return status.code().value_or(EXIT_FAILURE);
})
.map_err([&name](auto error) {
spdlog::warn("Running {} failed: {}", name, error.what());
return error;
})
.on_success([&name](auto status) {
spdlog::debug("Running {} finished. [Exited with {}]", name, status);
});
}
}
namespace bear {
Command::Command(const sys::Process::Builder& intercept, const sys::Process::Builder& citnames, fs::path output) noexcept
: ps::Command()
, intercept_(intercept)
, citnames_(citnames)
, output_(std::move(output))
{ }
[[nodiscard]] rust::Result Command::execute() const
{
auto result = ::execute(intercept_, "intercept");
std::error_code error_code;
if (fs::exists(output_, error_code)) {
::execute(citnames_, "citnames");
fs::remove(output_, error_code);
}
return result;
}
Application::Application()
: ps::ApplicationFromArgs(ps::ApplicationLogConfig("bear", "br"))
{ }
rust::Result Application::parse(int argc, const char **argv) const
{
const flags::Parser intercept_parser("intercept", cmd::VERSION, {
{cmd::intercept::FLAG_OUTPUT, {1, false, "path of the result file", {cmd::intercept::DEFAULT_OUTPUT}, std::nullopt}},
{cmd::intercept::FLAG_FORCE_PRELOAD, {0, false, "force to use library preload", std::nullopt, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_FORCE_WRAPPER, {0, false, "force to use compiler wrappers", std::nullopt, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_LIBRARY, {1, false, "path to the preload library", {cmd::library::DEFAULT_PATH}, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_WRAPPER, {1, false, "path to the wrapper executable", {cmd::wrapper::DEFAULT_PATH}, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_WRAPPER_DIR, {1, false, "path to the wrapper directory", {cmd::wrapper::DEFAULT_DIR_PATH}, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_COMMAND, {-1, true, "command to execute", std::nullopt, std::nullopt}}
});
const flags::Parser citnames_parser("citnames", cmd::VERSION, {
{cmd::citnames::FLAG_INPUT, {1, false, "path of the input file", {cmd::intercept::DEFAULT_OUTPUT}, std::nullopt}},
{cmd::citnames::FLAG_OUTPUT, {1, false, "path of the result file", {cmd::citnames::DEFAULT_OUTPUT}, std::nullopt}},
{cmd::citnames::FLAG_CONFIG, {1, false, "path of the config file", std::nullopt, std::nullopt}},
{cmd::citnames::FLAG_APPEND, {0, false, "append to output, instead of overwrite it", std::nullopt, std::nullopt}},
{cmd::citnames::FLAG_RUN_CHECKS, {0, false, "can run checks on the current host", std::nullopt, std::nullopt}}
});
const flags::Parser parser("bear", cmd::VERSION, {intercept_parser, citnames_parser}, {
{cmd::citnames::FLAG_OUTPUT, {1, false, "path of the result file", {cmd::citnames::DEFAULT_OUTPUT}, std::nullopt}},
{cmd::citnames::FLAG_APPEND, {0, false, "append result to an existing output file", std::nullopt, ADVANCED_GROUP}},
{cmd::citnames::FLAG_CONFIG, {1, false, "path of the config file", std::nullopt, ADVANCED_GROUP}},
{cmd::intercept::FLAG_FORCE_PRELOAD, {0, false, "force to use library preload", std::nullopt, ADVANCED_GROUP}},
{cmd::intercept::FLAG_FORCE_WRAPPER, {0, false, "force to use compiler wrappers", std::nullopt, ADVANCED_GROUP}},
{cmd::bear::FLAG_BEAR, {1, false, "path to the bear executable", {cmd::bear::DEFAULT_PATH}, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_LIBRARY, {1, false, "path to the preload library", {cmd::library::DEFAULT_PATH}, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_WRAPPER, {1, false, "path to the wrapper executable", {cmd::wrapper::DEFAULT_PATH}, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_WRAPPER_DIR, {1, false, "path to the wrapper directory", {cmd::wrapper::DEFAULT_DIR_PATH}, DEVELOPER_GROUP}},
{cmd::intercept::FLAG_COMMAND, {-1, true, "command to execute", std::nullopt, std::nullopt}}
});
return parser.parse_or_exit(argc, const_cast(argv));
}
rust::Result Application::command(const flags::Arguments& args, const char** envp) const
{
// Check if subcommand was called.
if (args.as_string(flags::COMMAND).is_ok()) {
if (auto citnames = cs::Citnames(log_config_); citnames.matches(args)) {
return citnames.subcommand(args, envp);
}
if (auto intercept = ic::Intercept(log_config_); intercept.matches(args)) {
return intercept.subcommand(args, envp);
}
return rust::Err(std::runtime_error("Invalid subcommand"));
}
// If there were no subcommand, then just execute the two one after the other.
// TODO: execute the two process parallel like the intercept output is the citnames input.
// `bear intercept -o - | bear citnames -i - -o compile_commands.json`
auto commands = args.as_string(cmd::citnames::FLAG_OUTPUT)
.map([](const auto& output) {
return fs::path(output).replace_extension(".events.json");
})
.unwrap_or(fs::path(cmd::citnames::DEFAULT_OUTPUT));
auto environment = sys::env::from(const_cast(envp));
auto intercept = prepare_intercept(args, environment, commands);
auto citnames = prepare_citnames(args, environment, commands);
return rust::merge(intercept, citnames)
.map([&commands](const auto& tuple) {
const auto& [intercept, citnames] = tuple;
return std::make_unique(intercept, citnames, commands);
});
}
}
rizsotto-Bear-14c2e01/source/bear/source/Application.h 0000664 0000000 0000000 00000003577 14767742337 0022764 0 ustar 00root root 0000000 0000000 /* Copyright (C) 2012-2024 by László Nagy
This file is part of Bear.
Bear is a tool to generate compilation database for clang tooling.
Bear is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Bear is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#pragma once
#include "config.h"
#include "libflags/Flags.h"
#include "libresult/Result.h"
#include "libsys/Environment.h"
#include "libsys/Process.h"
#include "libsys/Signal.h"
#include "libmain/ApplicationFromArgs.h"
#include
#include
#include
#include
#include
#include
namespace bear {
struct Command : ps::Command {
public:
Command(const sys::Process::Builder& intercept, const sys::Process::Builder& citnames, fs::path output) noexcept;
[[nodiscard]] rust::Result execute() const override;
NON_DEFAULT_CONSTRUCTABLE(Command)
NON_COPYABLE_NOR_MOVABLE(Command)
private:
sys::Process::Builder intercept_;
sys::Process::Builder citnames_;
fs::path output_;
};
struct Application : ps::ApplicationFromArgs {
Application();
rust::Result parse(int argc, const char **argv) const override;
rust::Result command(const flags::Arguments &args, const char **envp) const override;
};
}
rizsotto-Bear-14c2e01/source/citnames/ 0000775 0000000 0000000 00000000000 14767742337 0017726 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/citnames/CMakeLists.txt 0000664 0000000 0000000 00000005215 14767742337 0022471 0 ustar 00root root 0000000 0000000 # Create a static library, which is used for unit tests and the final shared library.
add_library(citnames_json_a OBJECT)
target_sources(citnames_json_a
PRIVATE
source/Configuration.cc
source/Output.cc
INTERFACE
$
)
target_link_libraries(citnames_json_a PUBLIC
result_a
shell_a
sys_a
fmt::fmt
spdlog::spdlog
nlohmann_json::nlohmann_json)
target_compile_options(citnames_json_a PRIVATE -fexceptions)
# Create a static library, which is used for unit tests and the final shared library.
add_library(citnames_a OBJECT)
target_include_directories(citnames_a PUBLIC source/ include/)
target_sources(citnames_a
PRIVATE
source/Citnames.cc
source/semantic/Build.cc
source/semantic/Common.cc
source/semantic/Parsers.cc
source/semantic/Semantic.cc
source/semantic/ToolAny.cc
source/semantic/ToolCrayFtnfe.cc
source/semantic/ToolClang.cc
source/semantic/ToolCuda.cc
source/semantic/ToolGcc.cc
source/semantic/ToolIntelFortran.cc
source/semantic/ToolWrapper.cc
source/semantic/ToolExtendingWrapper.cc
INTERFACE
$
)
target_link_libraries(citnames_a PUBLIC
main_a
citnames_json_a
events_db_a
domain_a
result_a
flags_a
sys_a
exec_a
fmt::fmt
spdlog::spdlog)
include(GNUInstallDirs)
# Markdown file is the source to the man file. Please modify that and generate
# the man file from it with pandoc.
#
# $ pandoc -s -t man bear-citnames.1.md -o bear-citnames.1
#
# This is not automated, because pandoc has big dependencies on different OS
# distributions and packaging would require to install those. Which might be
# too much effort to generate a single text file.
install(FILES man/bear-citnames.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
if (ENABLE_UNIT_TESTS)
add_executable(citnames_unit_test
test/OutputTest.cc
test/ParserTest.cc
test/ToolCrayFtnfeTest.cc
test/ToolClangTest.cc
test/ToolGccTest.cc
test/ToolIntelFortranTest.cc
test/ToolWrapperTest.cc
)
target_link_libraries(citnames_unit_test citnames_a)
target_link_libraries(citnames_unit_test citnames_json_a)
target_link_libraries(citnames_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT})
add_test(NAME bear::citnames_unit_test COMMAND $)
endif ()
rizsotto-Bear-14c2e01/source/citnames/include/ 0000775 0000000 0000000 00000000000 14767742337 0021351 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/citnames/include/citnames/ 0000775 0000000 0000000 00000000000 14767742337 0023154 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/citnames/include/citnames/citnames-forward.h 0000664 0000000 0000000 00000002200 14767742337 0026564 0 ustar 00root root 0000000 0000000 /* Copyright (C) 2023 by Samu698
This file is part of Bear.
Bear is a tool to generate compilation database for clang tooling.
Bear is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Bear is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#pragma once
#include "libmain/SubcommandFromArgs.h"
#include "libresult/Result.h"
namespace cs {
struct Citnames : ps::SubcommandFromArgs {
Citnames(const ps::ApplicationLogConfig&) noexcept;
rust::Result command(const flags::Arguments &args, const char **envp) const override;
NON_DEFAULT_CONSTRUCTABLE(Citnames)
};
}
rizsotto-Bear-14c2e01/source/citnames/man/ 0000775 0000000 0000000 00000000000 14767742337 0020501 5 ustar 00root root 0000000 0000000 rizsotto-Bear-14c2e01/source/citnames/man/bear-citnames.1 0000664 0000000 0000000 00000013512 14767742337 0023277 0 ustar 00root root 0000000 0000000 .\" Automatically generated by Pandoc 2.14.0.3
.\"
.TH "BEAR-CITNAMES" "1" "Jan 02, 2023" "Bear User Manuals" ""
.hy
.SH NAME
.PP
bear-citnames - deduce command semantic
.SH SYNOPSIS
.PP
bear citnames [\f[I]options\f[R]] --input --output
.SH DESCRIPTION
.PP
The name citnames comes from to reverse the word \[lq]semantic\[rq].
.PP
Because when you type a command, you know your intent.
The command execution is just a thing to achieve your goal.
This program takes the command which was executed, and try to find out
what the intent was to run that command.
It deduces the semantic of the command.
.PP
This is useful to generate a compilation database.
Citnames get a list of commands, and it creates a JSON compilation
database.
(This is currently the only output of the tool.)
.SH OPTIONS
.TP
--version
Print version number.
.TP
--help
Print help message.
.TP
--verbose
Enable verbose logging.
.TP
--input \f[I]file\f[R]
Specify input file.
(Default file name provided.) The input is a command execution list,
with some extra information.
The syntax is detailed in a separate section.
.TP
--output \f[I]file\f[R]
Specify output file.
(Default file name provided.) The output is currently a JSON compilation
database.
.TP
--append
Use previously generated output file and append the new entries to it.
This way you can run continuously during work, and it keeps the
compilation database up to date.
File deletion and addition are both considered.
But build process change (compiler flags change) might cause duplicate
entries.
.TP
--run-checks
Allow the program to verify file location checks on the current machine
it runs.
(Default value provided.
Run help to query it.) This is important if the execution list is not
from the current host.
.TP
--config \f[I]file\f[R]
Specify a configuration file.
The configuration file captures how the output should be formatted and
which entries it shall contain.
.SH EXIT STATUS
.PP
Citnames exit status is non-zero in case of IO problems, otherwise
it\[cq]s zero.
The exit status is independent of how many command it recognized or was
it recognized at all.
.SH OBSERVABILITY
.PP
Any insight about the command recognition logic can be observed with
\f[C]--verbose\f[R] flag on the standard error.
Otherwise, the command is silent.
.SH INPUT FILE
.PP
It\[cq]s a JSON file, with the command execution history.
(Plus some metadata, that is useful for debugging the application which
was produced it.) This file can be produced by the
\f[C]bear intercept\f[R] command, which records the process executions
of a build.
.PP
Read more about the syntax of the file in the
\f[C]bear-intercept(1)\f[R] man page.
.SH OUTPUT FILE
.PP
Currently, the only output format is the JSON compilation database.
Read more about the syntax of that in the \f[C]bear(1)\f[R] man page.
.SH CONFIG FILE
.PP
The config file influences the command recognition (by the section
\[lq]compilation\[rq]) and the output format (by the section
\[lq]output\[rq]).
.PP
The config file is optional.
The program will use default values, which can be dumped with the
\f[C]--verbose\f[R] flags.
.PP
Some parts of the file has overlap with the command line arguments.
If both present the command line argument overrides the config file
values.
.IP
.nf
\f[C]
{
\[dq]compilation\[dq]: {
\[dq]compilers_to_recognize\[dq]: [
{
\[dq]executable\[dq]: \[dq]/usr/bin/mpicc\[dq],
\[dq]flags_to_add\[dq]: [\[dq]-I/opt/MPI/include\[dq]],
\[dq]flags_to_remove\[dq]: [\[dq]-Wall\[dq]]
}
],
\[dq]compilers_to_exclude\[dq]: []
},
\[dq]output\[dq]: {
\[dq]content\[dq]: {
\[dq]include_only_existing_source\[dq]: true,
\[dq]paths_to_include\[dq]: [],
\[dq]paths_to_exclude\[dq]: [],
\[dq]duplicate_filter_fields\[dq]: \[dq]file_output\[dq]
},
\[dq]format\[dq]: {
\[dq]command_as_array\[dq]: true,
\[dq]drop_output_field\[dq]: false
}
}
}
\f[R]
.fi
.TP
\f[B]\f[CB]compilation.compilers_to_recognize\f[B]\f[R]
where compiler can be specified, which are not yet recognized by
default.
The \f[C]executable\f[R] is an absolute path to the compiler.
The \f[C]flags_to_add\f[R] is an optional attribute, which contains
flags which will append to the final output.
(It\[cq]s a good candidate to use this for adding OpenMPI compiler
wrapper flags from the \f[C]mpicc --showme:compile\f[R] output.) The
\f[C]flags_to_remove\f[R] is an optional attribute, where the given
flags will be removed for the final argument list.
(The flags checked for equality only, no regex match.
Flags with arguments are not good candidates to put here, because the
removal logic is too simple for that.)
.TP
\f[B]\f[CB]compilation.compilers_to_exclude\f[B]\f[R]
this is an optional list of executables (with absolute path) which needs
to be removed from the output.
.TP
\f[B]\f[CB]output.content\f[B]\f[R]
The \f[C]paths_to_include\f[R] and \f[C]paths_to_exclude\f[R] are for
filter out entries from these directories.
(Directory names can be absolute paths or relative to the current
working directory if the \f[C]--run-checks\f[R] flag passed.) The
\f[C]include_only_existing_source\f[R] allows or disables file check for
the output.
The \f[C]--run-checks\f[R] flag overrides this config value.
The \f[C]duplicate_filter_fields\f[R] select the method how duplicate
entries are detected in the output.
The possible values for this field are: \f[C]all\f[R], \f[C]file\f[R]
and \f[C]file_output\f[R].
.TP
\f[B]\f[CB]output.format\f[B]\f[R]
The \f[C]command_as_array\f[R] controls which command field is emitted
in the output.
True produces \f[C]arguments\f[R], false produces \f[C]command\f[R]
field.
The \f[C]drop_output_field\f[R] will disable the \f[C]output\f[R] field
from the output.
.SH SEE ALSO
.PP
\f[C]bear(1)\f[R], \f[C]bear-intercept(1)\f[R]
.SH COPYRIGHT
.PP
Copyright (C) 2012-2024 by L\['a]szl\['o] Nagy
.SH AUTHORS
L\['a]szl\['o] Nagy.
rizsotto-Bear-14c2e01/source/citnames/man/bear-citnames.1.md 0000664 0000000 0000000 00000012472 14767742337 0023702 0 ustar 00root root 0000000 0000000 % BEAR-CITNAMES(1) Bear User Manuals
% László Nagy
% Jan 02, 2023
# NAME
bear-citnames - deduce command semantic
# SYNOPSIS
bear citnames [*options*] \--input \