Skip to content

Commit

Permalink
rust: bear main drafted
Browse files Browse the repository at this point in the history
  • Loading branch information
rizsotto committed Sep 16, 2024
1 parent d39f18b commit 5d081f2
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 58 deletions.
1 change: 1 addition & 0 deletions rust/bear/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ path = "src/main.rs"
[dependencies]
intercept = { path = "../intercept" }
semantic = { path = "../semantic" }
json_compilation_db.workspace = true
thiserror.workspace = true
anyhow.workspace = true
lazy_static.workspace = true
Expand Down
6 changes: 3 additions & 3 deletions rust/bear/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ pub(crate) struct BuildCommand {

#[derive(Debug, PartialEq)]
pub(crate) struct BuildSemantic {
file_name: String,
append: bool,
pub file_name: String,
pub append: bool,
}

#[derive(Debug, PartialEq)]
pub(crate) struct BuildEvents {
file_name: String,
pub file_name: String,
}


Expand Down
31 changes: 1 addition & 30 deletions rust/bear/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,6 @@ const WRAPPER_EXECUTABLE_PATH: &str = env!("WRAPPER_EXECUTABLE_PATH");
/// mode: preload
/// output:
/// specification: bear
/// filter:
/// compilers:
/// with_paths:
/// - /usr/local/bin/cc
/// - /usr/local/bin/c++
/// source:
/// include_only_existing_files: true
/// ```
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Configuration {
Expand Down Expand Up @@ -205,6 +198,7 @@ pub enum Intercept {
Preload {
#[serde(default = "default_preload_library")]
path: PathBuf,
// TODO: add support for executables to recognize (as compiler)
},
}

Expand Down Expand Up @@ -248,8 +242,6 @@ pub enum Output {
},
#[serde(rename = "bear")]
Semantic {
#[serde(default)]
filter: Filter // FIXME: should have its own type and limit the filtering options
},
}

Expand Down Expand Up @@ -509,13 +501,6 @@ mod test {
path: /usr/local/lib/libexec.so
output:
specification: bear
filter:
compilers:
with_paths:
- /usr/local/bin/cc
- /usr/local/bin/c++
source:
include_only_existing_files: true
"#;

let result = Configuration::from_reader(content).unwrap();
Expand All @@ -525,20 +510,6 @@ mod test {
path: PathBuf::from("/usr/local/lib/libexec.so"),
},
output: Output::Semantic {
filter: Filter {
compilers: CompilerFilter {
with_paths: vec_of_pathbuf!["/usr/local/bin/cc", "/usr/local/bin/c++"],
with_arguments: vec![],
},
source: SourceFilter {
include_only_existing_files: true,
paths_to_include: vec![],
paths_to_exclude: vec![],
},
duplicates: DuplicateFilter {
by_fields: vec![OutputFields::File, OutputFields::Arguments],
}
},
},
schema: String::from("4.0"),
};
Expand Down
258 changes: 245 additions & 13 deletions rust/bear/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use std::fs::{File, OpenOptions};
use std::io::{BufReader, BufWriter, Read};
use std::path::PathBuf;
use std::process::ExitCode;
use log::{debug, LevelFilter};
use anyhow::Context;
use log::{LevelFilter};
use simple_logger::SimpleLogger;
use crate::command::Mode;
use crate::configuration::Configuration;
use json_compilation_db::Entry;
use serde_json::Error;
use intercept::ipc::Execution;
use crate::command::{BuildCommand, BuildEvents, BuildSemantic, Mode};
use semantic::tools;
use semantic::tools::{RecognitionResult, Semantic};
use semantic::events;
use semantic::filter;
use semantic::filter::EntryPredicateBuilder;

mod command;
mod configuration;
Expand All @@ -38,21 +49,19 @@ fn main() -> anyhow::Result<ExitCode> {
// Get the package name and version from Cargo
let pkg_name = env!("CARGO_PKG_NAME");
let pkg_version = env!("CARGO_PKG_VERSION");
debug!("{} v{}", pkg_name, pkg_version);
log::debug!("{} v{}", pkg_name, pkg_version);
// Print the arguments.
debug!("Arguments: {:?}", arguments);
log::debug!("Arguments: {:?}", arguments);
// Load the configuration.
let configuration = Configuration::load(&arguments.config)?;
debug!("Configuration: {:?}", configuration);
let configuration = configuration::Configuration::load(&arguments.config)?;
log::debug!("Configuration: {:?}", configuration);

// Run the application.
let result = match arguments.mode {
Mode::Intercept { input, output } => ExitCode::SUCCESS,
Mode::Semantic { input, output } => ExitCode::FAILURE,
Mode::All { input, output } => ExitCode::from(100),
};
let application = Application::configure(arguments, configuration)?;
let result = application.run();
log::debug!("Exit code: {:?}", result);

return Ok(result);
Ok(result)
}

/// Initializes the logging system.
Expand Down Expand Up @@ -81,3 +90,226 @@ fn prepare_logging(level: u8) -> anyhow::Result<()> {

Ok(())
}

/// Represent the application state.
enum Application {
Intercept {
input: BuildCommand,
output: BuildEvents,
intercept_config: configuration::Intercept,
},
Semantic {
event_source: EventFileReader,
semantic_recognition: SemanticRecognition,
semantic_transform: SemanticTransform,
entry_filter: filter::EntryPredicate,
output_writer: OutputWriter,
},
All {
input: BuildCommand,
output: BuildSemantic,
intercept_config: configuration::Intercept,
output_config: configuration::Output,
},
}

impl Application {
fn configure(arguments: command::Arguments, configuration: configuration::Configuration) -> anyhow::Result<Self> {
match arguments.mode {
Mode::Intercept { input, output } => {
let intercept_config = configuration.intercept;
let result = Application::Intercept { input, output, intercept_config };
Ok(result)
}
Mode::Semantic { input, output } => {
let event_source = EventFileReader::try_from(input)?;
let semantic_recognition = SemanticRecognition::try_from(&configuration)?;
let semantic_transform = SemanticTransform::try_from(&configuration)?;
let entry_filter = filter::EntryPredicate::try_from(&configuration)?;
let output_writer = OutputWriter::try_from(&output)?;
let result = Application::Semantic { event_source, semantic_recognition, semantic_transform, entry_filter, output_writer };
Ok(result)
}
Mode::All { input, output } => {
let intercept_config = configuration.intercept;
let output_config = configuration.output;
let result = Application::All { input, output, intercept_config, output_config };
Ok(result)
}
}
}

fn run(self) -> ExitCode {
match self {
Application::Intercept { input, output, intercept_config } => {
// TODO: Implement the intercept mode.
ExitCode::FAILURE
}
Application::Semantic { event_source, semantic_recognition, semantic_transform, mut entry_filter, output_writer } => {
let entries = event_source.generate()
.flat_map(|execution| semantic_recognition.recognize(execution))
.flat_map(|semantic| semantic_transform.transform(semantic))
.filter(|entry| entry_filter(entry));

match output_writer.run(entries) {
Ok(_) => ExitCode::SUCCESS,
Err(_) => ExitCode::FAILURE,
}
}
Application::All { input, output, intercept_config, output_config } => {
// TODO: Implement the all mode.
ExitCode::FAILURE
}
}
}
}

struct EventFileReader {
reader: BufReader<File>,
file_name: PathBuf,
}

impl TryFrom<BuildEvents> for EventFileReader {
type Error = anyhow::Error;

fn try_from(value: BuildEvents) -> Result<Self, Self::Error> {
let file_name = PathBuf::from(value.file_name);
let file = OpenOptions::new().read(true).open(file_name.as_path())
.with_context(|| format!("Failed to open file: {:?}", file_name))?;
let reader = BufReader::new(file);

Ok(EventFileReader { reader, file_name })
}
}

impl EventFileReader {
fn generate(self) -> impl Iterator<Item=Execution> {
events::from_reader(self.reader)
.flat_map(|candidate| {
match candidate {
Ok(execution) => Some(execution),
Err(error) => {
log::error!("Failed to read entry: {}", error);
None
}
}
})
}
}

struct SemanticRecognition {
tool: Box<dyn tools::Tool>,
}

impl TryFrom<&configuration::Configuration> for SemanticRecognition {
type Error = anyhow::Error;

fn try_from(value: &configuration::Configuration) -> Result<Self, Self::Error> {
let compilers_to_include = match &value.intercept {
configuration::Intercept::Wrapper { executables, .. } => {
executables.clone()
}
_ => {
vec![]
}
};
let compilers_to_exclude = match &value.output {
configuration::Output::Clang { filter, .. } => {
filter.compilers.with_paths.clone()
}
_ => {
vec![]
}
};
let tool = tools::from(compilers_to_include.as_slice(), compilers_to_exclude.as_slice());
Ok(SemanticRecognition { tool })
}
}

impl SemanticRecognition {
fn recognize(&self, execution: Execution) -> Option<Semantic> {
match self.tool.recognize(&execution) {
RecognitionResult::Recognized(Ok(Semantic::UnixCommand)) => {
log::debug!("execution recognized as unix command: {:?}", execution);
None
}
RecognitionResult::Recognized(Ok(Semantic::BuildCommand)) => {
log::debug!("execution recognized as build command: {:?}", execution);
None
}
RecognitionResult::Recognized(Ok(semantic)) => {
log::debug!("execution recognized as compiler call, {:?} : {:?}", semantic, execution);
Some(semantic)
}
RecognitionResult::Recognized(Err(reason)) => {
log::debug!("execution recognized with failure, {:?} : {:?}", reason, execution);
None
}
RecognitionResult::NotRecognized => {
log::debug!("execution not recognized: {:?}", execution);
None
}
}
}
}

struct SemanticTransform {}

impl TryFrom<&configuration::Configuration> for SemanticTransform {
type Error = anyhow::Error;

fn try_from(value: &configuration::Configuration) -> Result<Self, Self::Error> {
Ok(SemanticTransform {})
}
}

impl SemanticTransform {
fn transform(&self, semantic: Semantic) -> Vec<Entry> {
// TODO: Implement the transformation (adding arguments or remove).
let entries: Result<Vec<Entry>, anyhow::Error> = semantic.try_into();
entries.unwrap_or_else(|error| {
log::debug!("compiler call failed to convert to compilation db entry: {}", error);
vec![]
})
}
}

impl TryFrom<&configuration::Configuration> for filter::EntryPredicate {
type Error = anyhow::Error;

fn try_from(value: &configuration::Configuration) -> Result<Self, Self::Error> {
// TODO: Implement the filter.
Ok(EntryPredicateBuilder::filter_by_source_existence(false).build())
}
}

struct OutputWriter {
output: String,
}

impl TryFrom<&BuildSemantic> for OutputWriter {
type Error = anyhow::Error;

fn try_from(value: &BuildSemantic) -> Result<Self, Self::Error> {
// TODO: check if the file is writable and fail early if not.
// TODO: check if the directory exists and fail early if not.
Ok(OutputWriter { output: value.file_name.clone() })
}
}

impl OutputWriter {
fn run(&self, entries: impl Iterator<Item=Entry>) -> anyhow::Result<()> {
let temp: String = format!("{}.tmp", self.output);
// Create scope for the file, so it will be closed when the scope is over.
{
let file = File::create(&temp)
.with_context(|| format!("Failed to create file: {}", temp))?;
let buffer = BufWriter::new(file);
json_compilation_db::write(buffer, entries)?;
}
std::fs::rename(&temp, self.output.as_str())
.with_context(|| format!("Failed to rename file from '{}' to '{}'.", temp, self.output))?;

Ok(())
}
}
2 changes: 1 addition & 1 deletion rust/semantic/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub type EntryPredicate = Box<dyn FnMut(&Entry) -> bool>;
/// Represents a builder object that can be used to construct an entry predicate.
///
/// The builder can be used to combine multiple predicates using logical operators.
struct EntryPredicateBuilder {
pub struct EntryPredicateBuilder {
candidate: Option<EntryPredicate>,
}

Expand Down
10 changes: 5 additions & 5 deletions rust/semantic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

mod compilation;
mod configuration;
mod events;
mod filter;
mod tools;
pub mod compilation;
pub mod configuration;
pub mod events;
pub mod filter;
pub mod tools;
mod fixtures;
Loading

0 comments on commit 5d081f2

Please sign in to comment.