mirror of
https://github.com/rizsotto/Bear.git
synced 2025-12-12 20:35:47 +01:00
rust: modes module cleanup
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use bear::modes::{Combined, Intercept, Mode, Semantic};
|
||||
use bear::modes::Mode;
|
||||
use bear::modes::intercept::Intercept;
|
||||
use bear::modes::semantic::Semantic;
|
||||
use bear::modes::combined::Combined;
|
||||
use bear::{args, config};
|
||||
use std::env;
|
||||
use std::process::ExitCode;
|
||||
@@ -68,12 +71,9 @@ impl Application {
|
||||
Application::Semantic(semantic) => semantic.run(),
|
||||
Application::Combined(all) => all.run(),
|
||||
};
|
||||
match status {
|
||||
Ok(code) => code,
|
||||
Err(error) => {
|
||||
log::error!("Run failed: {}", error);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
status.unwrap_or_else(|error| {
|
||||
log::error!("Bear: {}", error);
|
||||
ExitCode::FAILURE
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use anyhow::{Context, Result};
|
||||
use bear::ipc::tcp::ReporterOnTcp;
|
||||
use bear::ipc::Reporter;
|
||||
use bear::ipc::{Event, Execution, ProcessId};
|
||||
use bear::modes::KEY_DESTINATION;
|
||||
use bear::modes::intercept::KEY_DESTINATION;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Implementation of the wrapper process.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
@@ -81,6 +82,17 @@ pub struct Execution {
|
||||
pub environment: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Execution {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Execution path={}, args=[{}]",
|
||||
self.executable.display(),
|
||||
self.arguments.join(",")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reporter id is a unique identifier for a reporter.
|
||||
///
|
||||
/// It is used to identify the process that sends the execution report.
|
||||
|
||||
91
rust/bear/src/modes/combined.rs
Normal file
91
rust/bear/src/modes/combined.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use crate::ipc::Envelope;
|
||||
use crate::modes::intercept::{CollectorService, InterceptEnvironment};
|
||||
use crate::modes::semantic::Recognition;
|
||||
use crate::modes::Mode;
|
||||
use crate::output::OutputWriter;
|
||||
use crate::semantic::transformation::Transformation;
|
||||
use crate::semantic::Transform;
|
||||
use crate::{args, config};
|
||||
use anyhow::Context;
|
||||
use std::process::ExitCode;
|
||||
|
||||
/// The all model is combining the intercept and semantic modes.
|
||||
pub struct Combined {
|
||||
command: args::BuildCommand,
|
||||
intercept_config: config::Intercept,
|
||||
semantic_recognition: Recognition,
|
||||
semantic_transform: Transformation,
|
||||
output_writer: OutputWriter,
|
||||
}
|
||||
|
||||
impl Combined {
|
||||
/// Create a new all mode instance.
|
||||
pub fn from(
|
||||
command: args::BuildCommand,
|
||||
output: args::BuildSemantic,
|
||||
config: config::Main,
|
||||
) -> anyhow::Result<Self> {
|
||||
let semantic_recognition = Recognition::try_from(&config)?;
|
||||
let semantic_transform = Transformation::from(&config.output);
|
||||
let output_writer = OutputWriter::configure(&output, &config.output)?;
|
||||
let intercept_config = config.intercept;
|
||||
|
||||
Ok(Self {
|
||||
command,
|
||||
intercept_config,
|
||||
semantic_recognition,
|
||||
semantic_transform,
|
||||
output_writer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Consumer the envelopes for analysis and write the result to the output file.
|
||||
/// This implements the pipeline of the semantic analysis. Same as the `Semantic` mode.
|
||||
fn consume_for_analysis(
|
||||
semantic_recognition: Recognition,
|
||||
semantic_transform: Transformation,
|
||||
output_writer: OutputWriter,
|
||||
envelopes: impl IntoIterator<Item = Envelope>,
|
||||
) -> anyhow::Result<()> {
|
||||
let entries = envelopes
|
||||
.into_iter()
|
||||
.map(|envelope| envelope.event.execution)
|
||||
.flat_map(|execution| semantic_recognition.apply(execution))
|
||||
.flat_map(|semantic| semantic_transform.apply(semantic));
|
||||
|
||||
output_writer.run(entries)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for Combined {
|
||||
/// Run the all mode by setting up the collector service and the intercept environment.
|
||||
/// The build command is executed in the intercept environment. The collected events are
|
||||
/// then processed by the semantic recognition and transformation. The result is written
|
||||
/// to the output file.
|
||||
///
|
||||
/// The exit code is based on the result of the build command.
|
||||
fn run(self) -> anyhow::Result<ExitCode> {
|
||||
let semantic_recognition = self.semantic_recognition;
|
||||
let semantic_transform = self.semantic_transform;
|
||||
let output_writer = self.output_writer;
|
||||
let service = CollectorService::new(move |envelopes| {
|
||||
Self::consume_for_analysis(
|
||||
semantic_recognition,
|
||||
semantic_transform,
|
||||
output_writer,
|
||||
envelopes,
|
||||
)
|
||||
})
|
||||
.with_context(|| "Failed to create the ipc service")?;
|
||||
let environment = InterceptEnvironment::new(&self.intercept_config, service.address())
|
||||
.with_context(|| "Failed to create the ipc environment")?;
|
||||
|
||||
let status = environment
|
||||
.execute_build_command(self.command)
|
||||
.with_context(|| "Failed to execute the build command")?;
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use anyhow::Context;
|
||||
use serde_json::de::IoRead;
|
||||
use serde_json::{Error, StreamDeserializer};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::args;
|
||||
use crate::ipc::{Envelope, Execution};
|
||||
|
||||
/// Responsible for reading the build events from the intercept mode.
|
||||
///
|
||||
/// The file syntax is defined by the `events` module, and the parsing logic is implemented there.
|
||||
/// Here we only handle the file opening and the error handling.
|
||||
pub struct EventFileReader {
|
||||
stream: Box<dyn Iterator<Item = Result<Envelope, Error>>>,
|
||||
}
|
||||
|
||||
impl TryFrom<args::BuildEvents> for EventFileReader {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
/// Open the file and create a new instance of the event file reader.
|
||||
///
|
||||
/// If the file cannot be opened, the error will be logged and escalated.
|
||||
fn try_from(value: args::BuildEvents) -> Result<Self, Self::Error> {
|
||||
let file_name = PathBuf::from(value.file_name);
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(file_name.as_path())
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open input file: {:?}", file_name))?;
|
||||
let stream = Box::new(StreamDeserializer::new(IoRead::new(file)));
|
||||
|
||||
Ok(EventFileReader { stream })
|
||||
}
|
||||
}
|
||||
|
||||
impl EventFileReader {
|
||||
/// Generate the build events from the file.
|
||||
///
|
||||
/// Returns an iterator over the build events. Any error during the reading
|
||||
/// of the file will be logged and the failed entries will be skipped.
|
||||
pub fn generate(self) -> impl Iterator<Item = Execution> {
|
||||
self.stream.filter_map(|result| match result {
|
||||
Ok(value) => Some(value.event.execution),
|
||||
Err(error) => {
|
||||
log::error!("Failed to read event: {:?}", error);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use super::{KEY_DESTINATION, KEY_PRELOAD_PATH};
|
||||
use super::Mode;
|
||||
use crate::ipc::tcp::CollectorOnTcp;
|
||||
use crate::ipc::{Collector, Envelope};
|
||||
use crate::{args, config};
|
||||
use anyhow::Context;
|
||||
use std::io::BufWriter;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitCode};
|
||||
use std::sync::mpsc::channel;
|
||||
@@ -11,6 +13,73 @@ use std::sync::mpsc::Receiver;
|
||||
use std::sync::Arc;
|
||||
use std::{env, thread};
|
||||
|
||||
/// Declare the environment variables used by the intercept mode.
|
||||
pub const KEY_DESTINATION: &str = "INTERCEPT_REPORTER_ADDRESS";
|
||||
pub const KEY_PRELOAD_PATH: &str = "LD_PRELOAD";
|
||||
|
||||
/// The intercept mode we are only capturing the build commands
|
||||
/// and write it into the output file.
|
||||
pub struct Intercept {
|
||||
command: args::BuildCommand,
|
||||
output: args::BuildEvents,
|
||||
config: config::Intercept,
|
||||
}
|
||||
|
||||
impl Intercept {
|
||||
/// Create a new intercept mode instance.
|
||||
pub fn from(
|
||||
command: args::BuildCommand,
|
||||
output: args::BuildEvents,
|
||||
config: config::Main,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
command,
|
||||
output,
|
||||
config: config.intercept,
|
||||
})
|
||||
}
|
||||
|
||||
/// Consume events and write them into the output file.
|
||||
fn write_to_file(
|
||||
output_file_name: String,
|
||||
envelopes: impl IntoIterator<Item = Envelope>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut writer = std::fs::File::create(&output_file_name)
|
||||
.map(BufWriter::new)
|
||||
.with_context(|| format!("Failed to create output file: {:?}", &output_file_name))?;
|
||||
for envelope in envelopes {
|
||||
serde_json::to_writer(&mut writer, &envelope).with_context(|| {
|
||||
format!("Failed to write execution report: {:?}", &output_file_name)
|
||||
})?;
|
||||
// TODO: add a newline character to separate the entries
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for Intercept {
|
||||
/// Run the intercept mode by setting up the collector service and
|
||||
/// the intercept environment. The build command is executed in the
|
||||
/// intercept environment.
|
||||
///
|
||||
/// The exit code is based on the result of the build command.
|
||||
fn run(self) -> anyhow::Result<ExitCode> {
|
||||
let output_file_name = self.output.file_name.clone();
|
||||
let service = CollectorService::new(move |envelopes| {
|
||||
Self::write_to_file(output_file_name, envelopes)
|
||||
})
|
||||
.with_context(|| "Failed to create the ipc service")?;
|
||||
let environment = InterceptEnvironment::new(&self.config, service.address())
|
||||
.with_context(|| "Failed to create the ipc environment")?;
|
||||
|
||||
let status = environment
|
||||
.execute_build_command(self.command)
|
||||
.with_context(|| "Failed to execute the build command")?;
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
/// The service is responsible for collecting the events from the supervised processes.
|
||||
///
|
||||
/// The service is implemented as TCP server that listens on a random port on the loopback
|
||||
|
||||
@@ -1,220 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
mod input;
|
||||
mod intercept;
|
||||
mod recognition;
|
||||
pub mod intercept;
|
||||
pub mod semantic;
|
||||
pub mod combined;
|
||||
|
||||
use crate::ipc::Envelope;
|
||||
use crate::output::OutputWriter;
|
||||
use crate::{args, config};
|
||||
use anyhow::Context;
|
||||
use input::EventFileReader;
|
||||
use intercept::{CollectorService, InterceptEnvironment};
|
||||
use recognition::Recognition;
|
||||
use std::io::BufWriter;
|
||||
use std::process::ExitCode;
|
||||
use crate::semantic::Transform;
|
||||
use crate::semantic::transformation::Transformation;
|
||||
|
||||
/// Declare the environment variables used by the intercept mode.
|
||||
pub const KEY_DESTINATION: &str = "INTERCEPT_REPORTER_ADDRESS";
|
||||
pub const KEY_PRELOAD_PATH: &str = "LD_PRELOAD";
|
||||
|
||||
/// The mode trait is used to run the application in different modes.
|
||||
pub trait Mode {
|
||||
fn run(self) -> anyhow::Result<ExitCode>;
|
||||
}
|
||||
|
||||
/// The intercept mode we are only capturing the build commands
|
||||
/// and write it into the output file.
|
||||
pub struct Intercept {
|
||||
command: args::BuildCommand,
|
||||
output: args::BuildEvents,
|
||||
config: config::Intercept,
|
||||
}
|
||||
|
||||
impl Intercept {
|
||||
/// Create a new intercept mode instance.
|
||||
pub fn from(
|
||||
command: args::BuildCommand,
|
||||
output: args::BuildEvents,
|
||||
config: config::Main,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
command,
|
||||
output,
|
||||
config: config.intercept,
|
||||
})
|
||||
}
|
||||
|
||||
/// Consume events and write them into the output file.
|
||||
fn write_to_file(
|
||||
output_file_name: String,
|
||||
envelopes: impl IntoIterator<Item = Envelope>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut writer = std::fs::File::create(&output_file_name)
|
||||
.map(BufWriter::new)
|
||||
.with_context(|| format!("Failed to create output file: {:?}", &output_file_name))?;
|
||||
for envelope in envelopes {
|
||||
serde_json::to_writer(&mut writer, &envelope).with_context(|| {
|
||||
format!("Failed to write execution report: {:?}", &output_file_name)
|
||||
})?;
|
||||
// TODO: add a newline character to separate the entries
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for Intercept {
|
||||
/// Run the intercept mode by setting up the collector service and
|
||||
/// the intercept environment. The build command is executed in the
|
||||
/// intercept environment.
|
||||
///
|
||||
/// The exit code is based on the result of the build command.
|
||||
fn run(self) -> anyhow::Result<ExitCode> {
|
||||
let output_file_name = self.output.file_name.clone();
|
||||
let service = CollectorService::new(move |envelopes| {
|
||||
Self::write_to_file(output_file_name, envelopes)
|
||||
})
|
||||
.with_context(|| "Failed to create the ipc service")?;
|
||||
let environment = InterceptEnvironment::new(&self.config, service.address())
|
||||
.with_context(|| "Failed to create the ipc environment")?;
|
||||
|
||||
let status = environment
|
||||
.execute_build_command(self.command)
|
||||
.with_context(|| "Failed to execute the build command")?;
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
/// The semantic mode we are deduct the semantic meaning of the
|
||||
/// executed commands from the build process.
|
||||
pub struct Semantic {
|
||||
event_source: EventFileReader,
|
||||
semantic_recognition: Recognition,
|
||||
semantic_transform: Transformation,
|
||||
output_writer: OutputWriter,
|
||||
}
|
||||
|
||||
impl Semantic {
|
||||
/// Create a new semantic mode instance.
|
||||
pub fn from(
|
||||
input: args::BuildEvents,
|
||||
output: args::BuildSemantic,
|
||||
config: config::Main,
|
||||
) -> anyhow::Result<Self> {
|
||||
let event_source = EventFileReader::try_from(input)?;
|
||||
let semantic_recognition = Recognition::try_from(&config)?;
|
||||
let semantic_transform = Transformation::from(&config.output);
|
||||
let output_writer = OutputWriter::configure(&output, &config.output)?;
|
||||
|
||||
Ok(Self {
|
||||
event_source,
|
||||
semantic_recognition,
|
||||
semantic_transform,
|
||||
output_writer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for Semantic {
|
||||
/// Run the semantic mode by generating the compilation database entries
|
||||
/// from the event source. The entries are then processed by the semantic
|
||||
/// recognition and transformation. The result is written to the output file.
|
||||
///
|
||||
/// The exit code is based on the result of the output writer.
|
||||
fn run(self) -> anyhow::Result<ExitCode> {
|
||||
// Set up the pipeline of compilation database entries.
|
||||
let entries = self
|
||||
.event_source
|
||||
.generate()
|
||||
.flat_map(|execution| self.semantic_recognition.apply(execution))
|
||||
.flat_map(|semantic| self.semantic_transform.apply(semantic));
|
||||
// Consume the entries and write them to the output file.
|
||||
// The exit code is based on the result of the output writer.
|
||||
match self.output_writer.run(entries) {
|
||||
Ok(_) => Ok(ExitCode::SUCCESS),
|
||||
Err(_) => Ok(ExitCode::FAILURE),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The all model is combining the intercept and semantic modes.
|
||||
pub struct Combined {
|
||||
command: args::BuildCommand,
|
||||
intercept_config: config::Intercept,
|
||||
semantic_recognition: Recognition,
|
||||
semantic_transform: Transformation,
|
||||
output_writer: OutputWriter,
|
||||
}
|
||||
|
||||
impl Combined {
|
||||
/// Create a new all mode instance.
|
||||
pub fn from(
|
||||
command: args::BuildCommand,
|
||||
output: args::BuildSemantic,
|
||||
config: config::Main,
|
||||
) -> anyhow::Result<Self> {
|
||||
let semantic_recognition = Recognition::try_from(&config)?;
|
||||
let semantic_transform = Transformation::from(&config.output);
|
||||
let output_writer = OutputWriter::configure(&output, &config.output)?;
|
||||
let intercept_config = config.intercept;
|
||||
|
||||
Ok(Self {
|
||||
command,
|
||||
intercept_config,
|
||||
semantic_recognition,
|
||||
semantic_transform,
|
||||
output_writer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Consumer the envelopes for analysis and write the result to the output file.
|
||||
/// This implements the pipeline of the semantic analysis. Same as the `Semantic` mode.
|
||||
fn consume_for_analysis(
|
||||
semantic_recognition: Recognition,
|
||||
semantic_transform: Transformation,
|
||||
output_writer: OutputWriter,
|
||||
envelopes: impl IntoIterator<Item = Envelope>,
|
||||
) -> anyhow::Result<()> {
|
||||
let entries = envelopes
|
||||
.into_iter()
|
||||
.map(|envelope| envelope.event.execution)
|
||||
.flat_map(|execution| semantic_recognition.apply(execution))
|
||||
.flat_map(|semantic| semantic_transform.apply(semantic));
|
||||
|
||||
output_writer.run(entries)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for Combined {
|
||||
/// Run the all mode by setting up the collector service and the intercept environment.
|
||||
/// The build command is executed in the intercept environment. The collected events are
|
||||
/// then processed by the semantic recognition and transformation. The result is written
|
||||
/// to the output file.
|
||||
///
|
||||
/// The exit code is based on the result of the build command.
|
||||
fn run(self) -> anyhow::Result<ExitCode> {
|
||||
let semantic_recognition = self.semantic_recognition;
|
||||
let semantic_transform = self.semantic_transform;
|
||||
let output_writer = self.output_writer;
|
||||
let service = CollectorService::new(move |envelopes| {
|
||||
Self::consume_for_analysis(
|
||||
semantic_recognition,
|
||||
semantic_transform,
|
||||
output_writer,
|
||||
envelopes,
|
||||
)
|
||||
})
|
||||
.with_context(|| "Failed to create the ipc service")?;
|
||||
let environment = InterceptEnvironment::new(&self.intercept_config, service.address())
|
||||
.with_context(|| "Failed to create the ipc environment")?;
|
||||
|
||||
let status = environment
|
||||
.execute_build_command(self.command)
|
||||
.with_context(|| "Failed to execute the build command")?;
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//! Responsible for recognizing the semantic meaning of the executed commands.
|
||||
//!
|
||||
//! The recognition logic is implemented in the `interpreters` module.
|
||||
//! Here we only handle the errors and logging them to the console.
|
||||
|
||||
use super::super::ipc;
|
||||
use super::super::semantic;
|
||||
use super::config;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub struct Recognition {
|
||||
interpreter: Box<dyn semantic::Interpreter>,
|
||||
}
|
||||
|
||||
impl TryFrom<&config::Main> for Recognition {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
/// 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.
|
||||
fn try_from(config: &config::Main) -> Result<Self, Self::Error> {
|
||||
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::Ignore::Always)
|
||||
.map(|compiler| compiler.path.clone())
|
||||
.collect(),
|
||||
_ => vec![],
|
||||
};
|
||||
let interpreter = semantic::interpreters::Builder::new()
|
||||
.compilers_to_recognize(compilers_to_include.as_slice())
|
||||
.compilers_to_exclude(compilers_to_exclude.as_slice())
|
||||
.build();
|
||||
|
||||
Ok(Recognition {
|
||||
interpreter: Box::new(interpreter),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Recognition {
|
||||
/// Simple call the semantic module to recognize the execution.
|
||||
/// Forward only the compiler calls, and log each recognition result.
|
||||
pub fn apply(&self, execution: ipc::Execution) -> Option<semantic::CompilerCall> {
|
||||
match self.interpreter.recognize(&execution) {
|
||||
semantic::Recognition::Success(semantic) => {
|
||||
log::debug!(
|
||||
"execution recognized as compiler call, {:?} : {:?}",
|
||||
semantic,
|
||||
execution
|
||||
);
|
||||
Some(semantic)
|
||||
}
|
||||
semantic::Recognition::Ignored => {
|
||||
log::debug!("execution recognized, but ignored: {:?}", execution);
|
||||
None
|
||||
}
|
||||
semantic::Recognition::Error(reason) => {
|
||||
log::debug!(
|
||||
"execution recognized with failure, {:?} : {:?}",
|
||||
reason,
|
||||
execution
|
||||
);
|
||||
None
|
||||
}
|
||||
semantic::Recognition::Unknown => {
|
||||
log::debug!("execution not recognized: {:?}", execution);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
167
rust/bear/src/modes/semantic.rs
Normal file
167
rust/bear/src/modes/semantic.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use crate::modes::Mode;
|
||||
use crate::output::OutputWriter;
|
||||
use crate::semantic::transformation::Transformation;
|
||||
use crate::semantic::Transform;
|
||||
use crate::{args, config};
|
||||
use super::super::ipc;
|
||||
use super::super::semantic;
|
||||
use crate::ipc::{Envelope, Execution};
|
||||
use std::process::ExitCode;
|
||||
use serde_json::de::IoRead;
|
||||
use serde_json::{Error, StreamDeserializer};
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use anyhow::Context;
|
||||
|
||||
/// The semantic mode we are deduct the semantic meaning of the
|
||||
/// executed commands from the build process.
|
||||
pub struct Semantic {
|
||||
event_source: EventFileReader,
|
||||
semantic_recognition: Recognition,
|
||||
semantic_transform: Transformation,
|
||||
output_writer: OutputWriter,
|
||||
}
|
||||
|
||||
impl Semantic {
|
||||
/// Create a new semantic mode instance.
|
||||
pub fn from(
|
||||
input: args::BuildEvents,
|
||||
output: args::BuildSemantic,
|
||||
config: config::Main,
|
||||
) -> anyhow::Result<Self> {
|
||||
let event_source = EventFileReader::try_from(input)?;
|
||||
let semantic_recognition = Recognition::try_from(&config)?;
|
||||
let semantic_transform = Transformation::from(&config.output);
|
||||
let output_writer = OutputWriter::configure(&output, &config.output)?;
|
||||
|
||||
Ok(Self {
|
||||
event_source,
|
||||
semantic_recognition,
|
||||
semantic_transform,
|
||||
output_writer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for Semantic {
|
||||
/// Run the semantic mode by generating the compilation database entries
|
||||
/// from the event source. The entries are then processed by the semantic
|
||||
/// recognition and transformation. The result is written to the output file.
|
||||
///
|
||||
/// The exit code is based on the result of the output writer.
|
||||
fn run(self) -> anyhow::Result<ExitCode> {
|
||||
// Set up the pipeline of compilation database entries.
|
||||
let entries = self
|
||||
.event_source
|
||||
.generate()
|
||||
.inspect(|execution| log::debug!("execution: {}", execution))
|
||||
.flat_map(|execution| self.semantic_recognition.apply(execution))
|
||||
.inspect(|semantic| log::debug!("semantic: {:?}", semantic))
|
||||
.flat_map(|semantic| self.semantic_transform.apply(semantic));
|
||||
// Consume the entries and write them to the output file.
|
||||
// The exit code is based on the result of the output writer.
|
||||
match self.output_writer.run(entries) {
|
||||
Ok(_) => Ok(ExitCode::SUCCESS),
|
||||
Err(_) => Ok(ExitCode::FAILURE),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Responsible for recognizing the semantic meaning of the executed commands.
|
||||
///
|
||||
/// The recognition logic is implemented in the `interpreters` module.
|
||||
/// Here we only handle the errors and logging them to the console.
|
||||
|
||||
pub struct Recognition {
|
||||
interpreter: Box<dyn semantic::Interpreter>,
|
||||
}
|
||||
|
||||
impl TryFrom<&config::Main> for Recognition {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
/// 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.
|
||||
fn try_from(config: &config::Main) -> Result<Self, Self::Error> {
|
||||
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::Ignore::Always)
|
||||
.map(|compiler| compiler.path.clone())
|
||||
.collect(),
|
||||
_ => vec![],
|
||||
};
|
||||
let interpreter = semantic::interpreters::Builder::new()
|
||||
.compilers_to_recognize(compilers_to_include.as_slice())
|
||||
.compilers_to_exclude(compilers_to_exclude.as_slice())
|
||||
.build();
|
||||
|
||||
Ok(Recognition {
|
||||
interpreter: Box::new(interpreter),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Recognition {
|
||||
/// Simple call the semantic module to recognize the execution.
|
||||
/// Forward only the compiler calls, and log each recognition result.
|
||||
pub fn apply(&self, execution: ipc::Execution) -> semantic::Recognition<semantic::CompilerCall> {
|
||||
self.interpreter.recognize(&execution)
|
||||
}
|
||||
}
|
||||
|
||||
/// Responsible for reading the build events from the intercept mode.
|
||||
///
|
||||
/// The file syntax is defined by the `events` module, and the parsing logic is implemented there.
|
||||
/// Here we only handle the file opening and the error handling.
|
||||
pub struct EventFileReader {
|
||||
stream: Box<dyn Iterator<Item = Result<Envelope, Error>>>,
|
||||
}
|
||||
|
||||
impl TryFrom<args::BuildEvents> for EventFileReader {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
/// Open the file and create a new instance of the event file reader.
|
||||
///
|
||||
/// If the file cannot be opened, the error will be logged and escalated.
|
||||
fn try_from(value: args::BuildEvents) -> Result<Self, Self::Error> {
|
||||
let file_name = PathBuf::from(value.file_name);
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(file_name.as_path())
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open input file: {:?}", file_name))?;
|
||||
let stream = Box::new(StreamDeserializer::new(IoRead::new(file)));
|
||||
|
||||
Ok(EventFileReader { stream })
|
||||
}
|
||||
}
|
||||
|
||||
impl EventFileReader {
|
||||
/// Generate the build events from the file.
|
||||
///
|
||||
/// Returns an iterator over the build events. Any error during the reading
|
||||
/// of the file will be logged and the failed entries will be skipped.
|
||||
pub fn generate(self) -> impl Iterator<Item = Execution> {
|
||||
self.stream.filter_map(|result| match result {
|
||||
Ok(value) => Some(value.event.execution),
|
||||
Err(error) => {
|
||||
log::error!("Failed to read event: {:?}", error);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ pub mod transformation;
|
||||
|
||||
use super::ipc::Execution;
|
||||
use std::path::PathBuf;
|
||||
use crate::semantic;
|
||||
|
||||
/// Represents an executed command semantic.
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -66,6 +65,18 @@ pub enum Recognition<T> {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl <T> IntoIterator for Recognition<T> {
|
||||
type Item = T;
|
||||
type IntoIter = std::option::IntoIter<T>;
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user