mirror of
https://github.com/rizsotto/Bear.git
synced 2025-12-12 20:35:47 +01:00
rust: config for source file filtering reviewed
This commit is contained in:
@@ -29,42 +29,46 @@
|
||||
//! executables:
|
||||
//! - /usr/bin/cc
|
||||
//! - /usr/bin/c++
|
||||
//! - /usr/bin/clang
|
||||
//! - /usr/bin/clang++
|
||||
//! output:
|
||||
//! specification: clang
|
||||
//! compilers:
|
||||
//! - path: /usr/local/bin/cc
|
||||
//! ignore: always
|
||||
//! - path: /usr/local/bin/c++
|
||||
//! - path: /usr/bin/cc
|
||||
//! ignore: never
|
||||
//! - path: /usr/bin/c++
|
||||
//! ignore: conditional
|
||||
//! arguments:
|
||||
//! match:
|
||||
//! - -###
|
||||
//! - path: /usr/local/bin/clang
|
||||
//! - path: /usr/bin/clang
|
||||
//! ignore: never
|
||||
//! arguments:
|
||||
//! add:
|
||||
//! - -DDEBUG
|
||||
//! remove:
|
||||
//! - -Wall
|
||||
//! - path: /usr/local/bin/clang++
|
||||
//! - path: /usr/bin/clang++
|
||||
//! arguments:
|
||||
//! remove:
|
||||
//! - -Wall
|
||||
//! filter:
|
||||
//! source:
|
||||
//! include_only_existing_files: true
|
||||
//! paths_to_include:
|
||||
//! - sources
|
||||
//! paths_to_exclude:
|
||||
//! - tests
|
||||
//! duplicates:
|
||||
//! by_fields:
|
||||
//! - file
|
||||
//! - directory
|
||||
//! sources:
|
||||
//! only_existing_files: true
|
||||
//! paths:
|
||||
//! - path: /opt/project/sources
|
||||
//! ignore: never
|
||||
//! - path: /opt/project/tests
|
||||
//! ignore: always
|
||||
//! duplicates:
|
||||
//! by_fields:
|
||||
//! - file
|
||||
//! - directory
|
||||
//! format:
|
||||
//! command_as_array: true
|
||||
//! drop_output_field: false
|
||||
//! use_absolute_path: false
|
||||
//! paths_as: canonical
|
||||
//! ```
|
||||
//!
|
||||
//! ```yaml
|
||||
@@ -298,7 +302,9 @@ pub enum Output {
|
||||
#[serde(default)]
|
||||
compilers: Vec<Compiler>,
|
||||
#[serde(default)]
|
||||
filter: Filter,
|
||||
sources: SourceFilter,
|
||||
#[serde(default)]
|
||||
duplicates: DuplicateFilter,
|
||||
#[serde(default)]
|
||||
format: Format,
|
||||
},
|
||||
@@ -311,7 +317,8 @@ impl Default for Output {
|
||||
fn default() -> Self {
|
||||
Output::Clang {
|
||||
compilers: vec![],
|
||||
filter: Filter::default(),
|
||||
sources: SourceFilter::default(),
|
||||
duplicates: DuplicateFilter::default(),
|
||||
format: Format::default(),
|
||||
}
|
||||
}
|
||||
@@ -323,14 +330,17 @@ impl Validate for Output {
|
||||
match self {
|
||||
Output::Clang {
|
||||
compilers,
|
||||
filter,
|
||||
sources,
|
||||
duplicates,
|
||||
format,
|
||||
} => {
|
||||
let compilers = compilers.validate()?;
|
||||
let filter = filter.validate()?;
|
||||
let sources = sources.validate()?;
|
||||
let duplicates = duplicates.validate()?;
|
||||
Ok(Output::Clang {
|
||||
compilers,
|
||||
filter,
|
||||
sources,
|
||||
duplicates,
|
||||
format,
|
||||
})
|
||||
}
|
||||
@@ -346,8 +356,8 @@ impl Validate for Output {
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Compiler {
|
||||
pub path: PathBuf,
|
||||
#[serde(default = "default_never_ignore")]
|
||||
pub ignore: Ignore,
|
||||
#[serde(default)]
|
||||
pub ignore: IgnoreOrConsider,
|
||||
#[serde(default)]
|
||||
pub arguments: Arguments,
|
||||
}
|
||||
@@ -371,19 +381,19 @@ impl Validate for Compiler {
|
||||
/// Validate the configuration of the compiler.
|
||||
fn validate(self) -> Result<Self> {
|
||||
match self.ignore {
|
||||
Ignore::Always if self.arguments != Arguments::default() => {
|
||||
IgnoreOrConsider::Always if self.arguments != Arguments::default() => {
|
||||
anyhow::bail!(
|
||||
"All arguments must be empty in always ignore mode. {:?}",
|
||||
self.path
|
||||
);
|
||||
}
|
||||
Ignore::Conditional if self.arguments.match_.is_empty() => {
|
||||
IgnoreOrConsider::Conditional if self.arguments.match_.is_empty() => {
|
||||
anyhow::bail!(
|
||||
"The match arguments cannot be empty in conditional ignore mode. {:?}",
|
||||
self.path
|
||||
);
|
||||
}
|
||||
Ignore::Never if !self.arguments.match_.is_empty() => {
|
||||
IgnoreOrConsider::Never if !self.arguments.match_.is_empty() => {
|
||||
anyhow::bail!(
|
||||
"The arguments must be empty in never ignore mode. {:?}",
|
||||
self.path
|
||||
@@ -401,16 +411,23 @@ impl Validate for Compiler {
|
||||
///
|
||||
/// The meaning of the possible values are:
|
||||
/// - Always: Always ignore the compiler call.
|
||||
/// - Conditional: Ignore the compiler call if the arguments match.
|
||||
/// - Never: Never ignore the compiler call. (Default)
|
||||
/// - Conditional: Ignore the compiler call if the arguments match.
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub enum Ignore {
|
||||
#[serde(rename = "always")]
|
||||
pub enum IgnoreOrConsider {
|
||||
#[serde(rename = "always", alias = "true")]
|
||||
Always,
|
||||
#[serde(rename = "never", alias = "false")]
|
||||
Never,
|
||||
#[serde(rename = "conditional")]
|
||||
Conditional,
|
||||
#[serde(rename = "never")]
|
||||
Never,
|
||||
}
|
||||
|
||||
/// The default ignore mode is never ignore.
|
||||
impl Default for IgnoreOrConsider {
|
||||
fn default() -> Self {
|
||||
IgnoreOrConsider::Never
|
||||
}
|
||||
}
|
||||
|
||||
/// Argument lists to match, add or remove.
|
||||
@@ -430,46 +447,49 @@ pub struct Arguments {
|
||||
pub remove: Vec<String>,
|
||||
}
|
||||
|
||||
/// Filter configuration is used to filter the compiler calls.
|
||||
///
|
||||
/// Allow to filter the compiler calls by compiler, source files and duplicates.
|
||||
///
|
||||
/// - Compilers: Specify on the compiler path and arguments.
|
||||
/// - Source: Specify the source file location.
|
||||
/// - Duplicates: Specify the fields of the JSON compilation database record to detect duplicates.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Filter {
|
||||
#[serde(default)]
|
||||
pub source: SourceFilter,
|
||||
#[serde(default)]
|
||||
pub duplicates: DuplicateFilter,
|
||||
}
|
||||
|
||||
impl Validate for Filter {
|
||||
/// Validate the configuration of the output writer.
|
||||
fn validate(self) -> Result<Self> {
|
||||
self.duplicates.validate().map(|duplicates| Filter {
|
||||
source: self.source,
|
||||
duplicates,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Source filter configuration is used to filter the compiler calls based on the source files.
|
||||
///
|
||||
/// Allow to filter the compiler calls based on the source files.
|
||||
///
|
||||
/// - Include only existing files: can be true or false.
|
||||
/// - Paths to include: Only include the compiler calls that compiles source files from this path.
|
||||
/// - Paths to exclude: Exclude the compiler calls that compiles source files from this path.
|
||||
/// - List of directories to include or exclude.
|
||||
/// (The order of these entries will imply the order of evaluation.)
|
||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||
pub struct SourceFilter {
|
||||
#[serde(default = "default_disabled")]
|
||||
pub include_only_existing_files: bool,
|
||||
pub only_existing_files: bool,
|
||||
#[serde(default)]
|
||||
pub paths_to_include: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub paths_to_exclude: Vec<PathBuf>,
|
||||
pub paths: Vec<DirectoryFilter>,
|
||||
}
|
||||
|
||||
impl Validate for SourceFilter {
|
||||
/// Fail when the same directory is in multiple times in the list.
|
||||
/// Otherwise, return the received source filter.
|
||||
fn validate(self) -> Result<Self> {
|
||||
let mut already_seen = HashSet::new();
|
||||
for directory in &self.paths {
|
||||
if !already_seen.insert(&directory.path) {
|
||||
anyhow::bail!("The directory {:?} is duplicated.", directory.path);
|
||||
}
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Directory filter configuration is used to filter the compiler calls based on
|
||||
/// the source file location.
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct DirectoryFilter {
|
||||
pub path: PathBuf,
|
||||
pub ignore: Ignore,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub enum Ignore {
|
||||
#[serde(rename = "always", alias = "true")]
|
||||
Always,
|
||||
#[serde(rename = "never", alias = "false")]
|
||||
Never,
|
||||
}
|
||||
|
||||
/// Duplicate filter configuration is used to filter the duplicate compiler calls.
|
||||
@@ -524,8 +544,8 @@ pub struct Format {
|
||||
pub command_as_array: bool,
|
||||
#[serde(default = "default_disabled")]
|
||||
pub drop_output_field: bool,
|
||||
#[serde(default = "default_disabled")]
|
||||
pub use_absolute_path: bool,
|
||||
#[serde(default)]
|
||||
pub paths_as: PathFormat,
|
||||
}
|
||||
|
||||
impl Default for Format {
|
||||
@@ -533,11 +553,29 @@ impl Default for Format {
|
||||
Format {
|
||||
command_as_array: true,
|
||||
drop_output_field: false,
|
||||
use_absolute_path: false,
|
||||
paths_as: PathFormat::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Path format configuration describes how the paths should be formatted.
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub enum PathFormat {
|
||||
#[serde(rename = "original", alias = "is")]
|
||||
Original,
|
||||
#[serde(rename = "absolute")]
|
||||
Absolute,
|
||||
#[serde(rename = "canonical")]
|
||||
Canonical,
|
||||
}
|
||||
|
||||
/// The default path format is the original path.
|
||||
impl Default for PathFormat {
|
||||
fn default() -> Self {
|
||||
PathFormat::Original
|
||||
}
|
||||
}
|
||||
|
||||
fn default_disabled() -> bool {
|
||||
false
|
||||
}
|
||||
@@ -561,11 +599,6 @@ fn default_preload_library() -> PathBuf {
|
||||
PathBuf::from(PRELOAD_LIBRARY_PATH)
|
||||
}
|
||||
|
||||
/// The default don't ignore the compiler.
|
||||
fn default_never_ignore() -> Ignore {
|
||||
Ignore::Never
|
||||
}
|
||||
|
||||
// Custom deserialization function to validate the schema version
|
||||
fn validate_schema_version<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
|
||||
where
|
||||
@@ -610,42 +643,46 @@ mod test {
|
||||
executables:
|
||||
- /usr/bin/cc
|
||||
- /usr/bin/c++
|
||||
- /usr/bin/clang
|
||||
- /usr/bin/clang++
|
||||
output:
|
||||
specification: clang
|
||||
compilers:
|
||||
- path: /usr/local/bin/cc
|
||||
ignore: always
|
||||
- path: /usr/local/bin/c++
|
||||
- path: /usr/bin/cc
|
||||
ignore: never
|
||||
- path: /usr/bin/c++
|
||||
ignore: conditional
|
||||
arguments:
|
||||
match:
|
||||
- -###
|
||||
- path: /usr/local/bin/clang
|
||||
- path: /usr/bin/clang
|
||||
ignore: never
|
||||
arguments:
|
||||
add:
|
||||
- -DDEBUG
|
||||
remove:
|
||||
- -Wall
|
||||
- path: /usr/local/bin/clang++
|
||||
- path: /usr/bin/clang++
|
||||
arguments:
|
||||
remove:
|
||||
- -Wall
|
||||
filter:
|
||||
source:
|
||||
include_only_existing_files: true
|
||||
paths_to_include:
|
||||
- sources
|
||||
paths_to_exclude:
|
||||
- tests
|
||||
duplicates:
|
||||
by_fields:
|
||||
- file
|
||||
- directory
|
||||
sources:
|
||||
only_existing_files: true
|
||||
paths:
|
||||
- path: /opt/project/sources
|
||||
ignore: never
|
||||
- path: /opt/project/tests
|
||||
ignore: always
|
||||
duplicates:
|
||||
by_fields:
|
||||
- file
|
||||
- directory
|
||||
format:
|
||||
command_as_array: true
|
||||
drop_output_field: false
|
||||
use_absolute_path: true
|
||||
paths_as: canonical
|
||||
"#;
|
||||
|
||||
let result = Main::from_reader(content).unwrap();
|
||||
@@ -654,26 +691,36 @@ mod test {
|
||||
intercept: Intercept::Wrapper {
|
||||
path: default_wrapper_executable(),
|
||||
directory: PathBuf::from("/tmp"),
|
||||
executables: vec_of_pathbuf!["/usr/bin/cc", "/usr/bin/c++"],
|
||||
executables: vec_of_pathbuf![
|
||||
"/usr/bin/cc",
|
||||
"/usr/bin/c++",
|
||||
"/usr/bin/clang",
|
||||
"/usr/bin/clang++"
|
||||
],
|
||||
},
|
||||
output: Output::Clang {
|
||||
compilers: vec![
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/cc"),
|
||||
ignore: Ignore::Always,
|
||||
ignore: IgnoreOrConsider::Always,
|
||||
arguments: Arguments::default(),
|
||||
},
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/c++"),
|
||||
ignore: Ignore::Conditional,
|
||||
path: PathBuf::from("/usr/bin/cc"),
|
||||
ignore: IgnoreOrConsider::Never,
|
||||
arguments: Arguments::default(),
|
||||
},
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/bin/c++"),
|
||||
ignore: IgnoreOrConsider::Conditional,
|
||||
arguments: Arguments {
|
||||
match_: vec_of_strings!["-###"],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/clang"),
|
||||
ignore: Ignore::Never,
|
||||
path: PathBuf::from("/usr/bin/clang"),
|
||||
ignore: IgnoreOrConsider::Never,
|
||||
arguments: Arguments {
|
||||
add: vec_of_strings!["-DDEBUG"],
|
||||
remove: vec_of_strings!["-Wall"],
|
||||
@@ -681,28 +728,34 @@ mod test {
|
||||
},
|
||||
},
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/clang++"),
|
||||
ignore: Ignore::Never,
|
||||
path: PathBuf::from("/usr/bin/clang++"),
|
||||
ignore: IgnoreOrConsider::Never,
|
||||
arguments: Arguments {
|
||||
remove: vec_of_strings!["-Wall"],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
],
|
||||
filter: Filter {
|
||||
source: SourceFilter {
|
||||
include_only_existing_files: true,
|
||||
paths_to_include: vec_of_pathbuf!["sources"],
|
||||
paths_to_exclude: vec_of_pathbuf!["tests"],
|
||||
},
|
||||
duplicates: DuplicateFilter {
|
||||
by_fields: vec![OutputFields::File, OutputFields::Directory],
|
||||
},
|
||||
sources: SourceFilter {
|
||||
only_existing_files: true,
|
||||
paths: vec![
|
||||
DirectoryFilter {
|
||||
path: PathBuf::from("/opt/project/sources"),
|
||||
ignore: Ignore::Never,
|
||||
},
|
||||
DirectoryFilter {
|
||||
path: PathBuf::from("/opt/project/tests"),
|
||||
ignore: Ignore::Always,
|
||||
},
|
||||
],
|
||||
},
|
||||
duplicates: DuplicateFilter {
|
||||
by_fields: vec![OutputFields::File, OutputFields::Directory],
|
||||
},
|
||||
format: Format {
|
||||
command_as_array: true,
|
||||
drop_output_field: false,
|
||||
use_absolute_path: true,
|
||||
paths_as: PathFormat::Canonical,
|
||||
},
|
||||
},
|
||||
schema: String::from("4.0"),
|
||||
@@ -723,13 +776,12 @@ mod test {
|
||||
- /usr/bin/c++
|
||||
output:
|
||||
specification: clang
|
||||
filter:
|
||||
source:
|
||||
include_only_existing_files: true
|
||||
duplicates:
|
||||
by_fields:
|
||||
- file
|
||||
- directory
|
||||
sources:
|
||||
only_existing_files: true
|
||||
duplicates:
|
||||
by_fields:
|
||||
- file
|
||||
- directory
|
||||
format:
|
||||
command_as_array: true
|
||||
"#;
|
||||
@@ -744,20 +796,17 @@ mod test {
|
||||
},
|
||||
output: Output::Clang {
|
||||
compilers: vec![],
|
||||
filter: Filter {
|
||||
source: SourceFilter {
|
||||
include_only_existing_files: true,
|
||||
paths_to_include: vec_of_pathbuf![],
|
||||
paths_to_exclude: vec_of_pathbuf![],
|
||||
},
|
||||
duplicates: DuplicateFilter {
|
||||
by_fields: vec![OutputFields::File, OutputFields::Directory],
|
||||
},
|
||||
sources: SourceFilter {
|
||||
only_existing_files: true,
|
||||
paths: vec![],
|
||||
},
|
||||
duplicates: DuplicateFilter {
|
||||
by_fields: vec![OutputFields::File, OutputFields::Directory],
|
||||
},
|
||||
format: Format {
|
||||
command_as_array: true,
|
||||
drop_output_field: false,
|
||||
use_absolute_path: false,
|
||||
paths_as: PathFormat::Original,
|
||||
},
|
||||
},
|
||||
schema: String::from("4.0"),
|
||||
@@ -807,12 +856,11 @@ mod test {
|
||||
ignore: always
|
||||
- path: /usr/local/bin/clang++
|
||||
ignore: always
|
||||
filter:
|
||||
source:
|
||||
include_only_existing_files: false
|
||||
duplicates:
|
||||
by_fields:
|
||||
- file
|
||||
sources:
|
||||
only_existing_files: false
|
||||
duplicates:
|
||||
by_fields:
|
||||
- file
|
||||
format:
|
||||
command_as_array: true
|
||||
drop_output_field: true
|
||||
@@ -829,39 +877,36 @@ mod test {
|
||||
compilers: vec![
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/cc"),
|
||||
ignore: Ignore::Never,
|
||||
ignore: IgnoreOrConsider::Never,
|
||||
arguments: Arguments::default(),
|
||||
},
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/c++"),
|
||||
ignore: Ignore::Never,
|
||||
ignore: IgnoreOrConsider::Never,
|
||||
arguments: Arguments::default(),
|
||||
},
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/clang"),
|
||||
ignore: Ignore::Always,
|
||||
ignore: IgnoreOrConsider::Always,
|
||||
arguments: Arguments::default(),
|
||||
},
|
||||
Compiler {
|
||||
path: PathBuf::from("/usr/local/bin/clang++"),
|
||||
ignore: Ignore::Always,
|
||||
ignore: IgnoreOrConsider::Always,
|
||||
arguments: Arguments::default(),
|
||||
},
|
||||
],
|
||||
filter: Filter {
|
||||
source: SourceFilter {
|
||||
include_only_existing_files: false,
|
||||
paths_to_include: vec_of_pathbuf![],
|
||||
paths_to_exclude: vec_of_pathbuf![],
|
||||
},
|
||||
duplicates: DuplicateFilter {
|
||||
by_fields: vec![OutputFields::File],
|
||||
},
|
||||
sources: SourceFilter {
|
||||
only_existing_files: false,
|
||||
paths: vec![],
|
||||
},
|
||||
duplicates: DuplicateFilter {
|
||||
by_fields: vec![OutputFields::File],
|
||||
},
|
||||
format: Format {
|
||||
command_as_array: true,
|
||||
drop_output_field: true,
|
||||
use_absolute_path: false,
|
||||
paths_as: PathFormat::Original,
|
||||
},
|
||||
},
|
||||
schema: String::from("4.0"),
|
||||
@@ -878,7 +923,8 @@ mod test {
|
||||
intercept: Intercept::default(),
|
||||
output: Output::Clang {
|
||||
compilers: vec![],
|
||||
filter: Filter::default(),
|
||||
sources: SourceFilter::default(),
|
||||
duplicates: DuplicateFilter::default(),
|
||||
format: Format::default(),
|
||||
},
|
||||
schema: String::from(SUPPORTED_SCHEMA_VERSION),
|
||||
|
||||
@@ -80,11 +80,17 @@ impl OutputWriterImpl {
|
||||
) -> anyhow::Result<OutputWriterImpl> {
|
||||
// TODO: This method should fail early if the output file is not writable.
|
||||
match config {
|
||||
config::Output::Clang { format, filter, .. } => {
|
||||
config::Output::Clang {
|
||||
format,
|
||||
sources,
|
||||
duplicates,
|
||||
..
|
||||
} => {
|
||||
let result = ClangOutputWriter {
|
||||
output: PathBuf::from(&args.file_name),
|
||||
append: args.append,
|
||||
filter: filter.clone(),
|
||||
source_filter: sources.clone(),
|
||||
duplicate_filter: duplicates.clone(),
|
||||
command_as_array: format.command_as_array,
|
||||
formatter: From::from(format),
|
||||
};
|
||||
@@ -127,7 +133,8 @@ impl OutputWriter for SemanticOutputWriter {
|
||||
pub(crate) struct ClangOutputWriter {
|
||||
output: PathBuf,
|
||||
append: bool,
|
||||
filter: config::Filter,
|
||||
source_filter: config::SourceFilter,
|
||||
duplicate_filter: config::DuplicateFilter,
|
||||
command_as_array: bool,
|
||||
formatter: output::formatter::EntryFormatter,
|
||||
}
|
||||
@@ -162,8 +169,11 @@ impl ClangOutputWriter {
|
||||
entries: impl Iterator<Item = output::clang::Entry>,
|
||||
) -> anyhow::Result<()> {
|
||||
// Filter out the entries as per the configuration.
|
||||
let filter: output::filter::EntryPredicate = TryFrom::try_from(&self.filter)?;
|
||||
let filtered_entries = entries.filter(filter);
|
||||
let mut source_filter: output::filter::EntryPredicate = From::from(&self.source_filter);
|
||||
let mut duplicate_filter: output::filter::EntryPredicate =
|
||||
From::from(&self.duplicate_filter);
|
||||
let filtered_entries =
|
||||
entries.filter(move |entry| source_filter(entry) && duplicate_filter(entry));
|
||||
// Write the entries to a temporary file.
|
||||
self.write_into_temporary_compilation_db(filtered_entries)
|
||||
.and_then(|temp| {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::config;
|
||||
use crate::output::clang::Entry;
|
||||
@@ -14,26 +14,33 @@ use builder::EntryPredicateBuilder as Builder;
|
||||
/// If the predicate returns `false`, the entry is excluded from the result set.
|
||||
pub type EntryPredicate = Box<dyn FnMut(&Entry) -> bool>;
|
||||
|
||||
impl TryFrom<&config::Filter> for EntryPredicate {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
impl From<&config::SourceFilter> for EntryPredicate {
|
||||
/// Create a filter from the configuration.
|
||||
fn try_from(config: &config::Filter) -> Result<Self, Self::Error> {
|
||||
// - Check if the source file exists
|
||||
// - Check if the source file is not in the exclude list of the configuration
|
||||
// - Check if the source file is in the include list of the configuration
|
||||
let source_exist_check =
|
||||
Builder::filter_by_source_existence(config.source.include_only_existing_files);
|
||||
let source_paths_to_exclude =
|
||||
Builder::filter_by_source_paths(&config.source.paths_to_exclude);
|
||||
let source_paths_to_include =
|
||||
Builder::filter_by_source_paths(&config.source.paths_to_include);
|
||||
let source_checks = source_exist_check & !source_paths_to_exclude & source_paths_to_include;
|
||||
// - Check if the entry is not a duplicate based on the fields of the configuration
|
||||
let hash_function = create_hash(&config.duplicates.by_fields);
|
||||
let duplicates = Builder::filter_duplicate_entries(hash_function);
|
||||
fn from(config: &config::SourceFilter) -> Self {
|
||||
let source_exist_check = Builder::filter_by_source_existence(config.only_existing_files);
|
||||
|
||||
Ok((source_checks & duplicates).build())
|
||||
let mut builder = Builder::new();
|
||||
for config::DirectoryFilter { path, ignore } in &config.paths {
|
||||
let filter = Builder::filter_by_source_path(path);
|
||||
match ignore {
|
||||
config::Ignore::Always => {
|
||||
builder = builder & !filter;
|
||||
}
|
||||
config::Ignore::Never => {
|
||||
builder = builder & filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(source_exist_check & builder).build()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&config::DuplicateFilter> for EntryPredicate {
|
||||
/// Create a filter from the configuration.
|
||||
fn from(config: &config::DuplicateFilter) -> Self {
|
||||
let hash_function = create_hash(&config.by_fields);
|
||||
Builder::filter_duplicate_entries(hash_function).build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +65,7 @@ mod builder {
|
||||
|
||||
/// Construct a predicate builder that is empty.
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { candidate: None }
|
||||
}
|
||||
|
||||
@@ -75,13 +82,9 @@ mod builder {
|
||||
|
||||
/// Create a predicate that filters out entries
|
||||
/// that are not using any of the given source paths.
|
||||
pub(super) fn filter_by_source_paths(paths: &[PathBuf]) -> Self {
|
||||
if paths.is_empty() {
|
||||
Self::new()
|
||||
} else {
|
||||
let owned_paths: Vec<PathBuf> = paths.to_vec();
|
||||
Self::from(move |entry| owned_paths.iter().any(|path| entry.file.starts_with(path)))
|
||||
}
|
||||
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
|
||||
@@ -172,13 +175,25 @@ mod builder {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{vec_of_pathbuf, vec_of_strings};
|
||||
use crate::vec_of_strings;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_filter_by_source_paths() {
|
||||
let paths_to_include = vec_of_pathbuf!["/home/user/project/source"];
|
||||
let paths_to_exclude = vec_of_pathbuf!["/home/user/project/test"];
|
||||
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 input: Vec<Entry> = vec![
|
||||
Entry {
|
||||
@@ -197,10 +212,7 @@ mod builder {
|
||||
|
||||
let expected: Vec<Entry> = vec![input[0].clone()];
|
||||
|
||||
let sut: EntryPredicate =
|
||||
(EntryPredicateBuilder::filter_by_source_paths(&paths_to_include)
|
||||
& !EntryPredicateBuilder::filter_by_source_paths(&paths_to_exclude))
|
||||
.build();
|
||||
let sut: EntryPredicate = From::from(&config);
|
||||
let result: Vec<Entry> = input.into_iter().filter(sut).collect();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -5,22 +5,23 @@ 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,
|
||||
use_absolute_path: 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 use_absolute_path = config.use_absolute_path;
|
||||
let path_format = config.paths_as.clone();
|
||||
|
||||
Self {
|
||||
drop_output_field,
|
||||
use_absolute_path,
|
||||
path_format,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,15 +67,17 @@ impl EntryFormatter {
|
||||
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.filter(|_| !self.drop_output_field).and_then(|it| {
|
||||
// FIXME: Conversion failures are ignored silently.
|
||||
self.format_path(it.as_path(), working_dir)
|
||||
.ok()
|
||||
.map(PathBuf::from)
|
||||
}),
|
||||
output: output_result,
|
||||
arguments: Self::format_arguments(compiler, &source, &flags, output_clone)?,
|
||||
})
|
||||
}
|
||||
@@ -108,15 +111,20 @@ impl EntryFormatter {
|
||||
Ok(arguments)
|
||||
}
|
||||
|
||||
fn format_path<'a>(&self, path: &'a Path, root: &Path) -> std::io::Result<Cow<'a, Path>> {
|
||||
if self.use_absolute_path {
|
||||
fn format_path<'a>(&self, path: &'a Path, root: &Path) -> io::Result<Cow<'a, Path>> {
|
||||
// Will compute the absolute path if needed.
|
||||
let absolute = || {
|
||||
if path.is_absolute() {
|
||||
path.absolutize()
|
||||
} else {
|
||||
path.absolutize_from(root)
|
||||
}
|
||||
} else {
|
||||
Ok(Cow::from(path))
|
||||
};
|
||||
|
||||
match self.path_format {
|
||||
config::PathFormat::Original => Ok(Cow::from(path)),
|
||||
config::PathFormat::Absolute => absolute(),
|
||||
config::PathFormat::Canonical => absolute()?.canonicalize().map(Cow::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,33 +143,26 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_non_compilations() {
|
||||
let format = config::Format {
|
||||
command_as_array: true,
|
||||
drop_output_field: false,
|
||||
use_absolute_path: false,
|
||||
};
|
||||
|
||||
let expected: Vec<Entry> = vec![];
|
||||
|
||||
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<Entry> = vec![];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_source_compilation() {
|
||||
let format = config::Format {
|
||||
command_as_array: true,
|
||||
drop_output_field: false,
|
||||
use_absolute_path: false,
|
||||
};
|
||||
|
||||
let input = semantic::CompilerCall {
|
||||
compiler: PathBuf::from("/usr/bin/clang"),
|
||||
working_dir: PathBuf::from("/home/user"),
|
||||
@@ -172,43 +173,34 @@ mod test {
|
||||
}],
|
||||
};
|
||||
|
||||
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")),
|
||||
}];
|
||||
|
||||
let sut: EntryFormatter = (&format).into();
|
||||
let result = sut.apply(input);
|
||||
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,
|
||||
use_absolute_path: false,
|
||||
};
|
||||
|
||||
let input = 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"],
|
||||
},
|
||||
],
|
||||
paths_as: config::PathFormat::Original,
|
||||
};
|
||||
let sut: EntryFormatter = (&format).into();
|
||||
let result = sut.apply(input);
|
||||
|
||||
let expected = vec![
|
||||
Entry {
|
||||
@@ -224,37 +216,20 @@ mod test {
|
||||
output: None,
|
||||
},
|
||||
];
|
||||
|
||||
let sut: EntryFormatter = (&format).into();
|
||||
let result = sut.apply(input);
|
||||
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,
|
||||
use_absolute_path: true,
|
||||
};
|
||||
|
||||
let input = 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"],
|
||||
},
|
||||
],
|
||||
paths_as: config::PathFormat::Absolute,
|
||||
};
|
||||
let sut: EntryFormatter = (&format).into();
|
||||
let result = sut.apply(input);
|
||||
|
||||
let expected = vec![
|
||||
Entry {
|
||||
@@ -270,9 +245,26 @@ mod test {
|
||||
output: None,
|
||||
},
|
||||
];
|
||||
|
||||
let sut: EntryFormatter = (&format).into();
|
||||
let result = sut.apply(input);
|
||||
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"],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl Builder {
|
||||
let compilers_to_exclude = match &config.output {
|
||||
config::Output::Clang { compilers, .. } => compilers
|
||||
.iter()
|
||||
.filter(|compiler| compiler.ignore == config::Ignore::Always)
|
||||
.filter(|compiler| compiler.ignore == config::IgnoreOrConsider::Always)
|
||||
.map(|compiler| compiler.path.clone())
|
||||
.collect(),
|
||||
_ => vec![],
|
||||
|
||||
@@ -40,11 +40,11 @@ impl Transform for Transformation {
|
||||
} = &input;
|
||||
match self.lookup(compiler) {
|
||||
Some(config::Compiler {
|
||||
ignore: config::Ignore::Always,
|
||||
ignore: config::IgnoreOrConsider::Always,
|
||||
..
|
||||
}) => None,
|
||||
Some(config::Compiler {
|
||||
ignore: config::Ignore::Conditional,
|
||||
ignore: config::IgnoreOrConsider::Conditional,
|
||||
arguments,
|
||||
..
|
||||
}) => {
|
||||
@@ -55,7 +55,7 @@ impl Transform for Transformation {
|
||||
}
|
||||
}
|
||||
Some(config::Compiler {
|
||||
ignore: config::Ignore::Never,
|
||||
ignore: config::IgnoreOrConsider::Never,
|
||||
arguments,
|
||||
..
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user