Skip to content

Commit eb5343c

Browse files
committed
rust: event file read and write methods in the same module
1 parent 1bd952d commit eb5343c

File tree

5 files changed

+184
-72
lines changed

5 files changed

+184
-72
lines changed

rust/bear/src/modes/combined.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,9 @@ impl Mode for Combined {
7676
Self::consume_for_analysis(interpreter, semantic_transform, output_writer, envelopes)
7777
})
7878
.with_context(|| "Failed to create the ipc service")?;
79-
let environment = InterceptEnvironment::new(&self.intercept_config, service.address())
80-
.with_context(|| "Failed to create the ipc environment")?;
8179

82-
let status = environment
80+
let status = InterceptEnvironment::new(&self.intercept_config, service.address())
81+
.with_context(|| "Failed to create the ipc environment")?
8382
.execute_build_command(self.command)
8483
.with_context(|| "Failed to execute the build command")?;
8584

rust/bear/src/modes/intercept.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
use super::Mode;
44
use crate::ipc::tcp::CollectorOnTcp;
55
use crate::ipc::{Collector, Envelope};
6+
use crate::output::event::write;
67
use crate::{args, config};
78
use anyhow::Context;
9+
use std::fs::OpenOptions;
810
use std::io::BufWriter;
911
use std::path::{Path, PathBuf};
1012
use std::process::{Command, ExitCode};
@@ -41,18 +43,19 @@ impl Intercept {
4143

4244
/// Consume events and write them into the output file.
4345
fn write_to_file(
44-
output_file_name: String,
46+
output_file_name: PathBuf,
4547
envelopes: impl IntoIterator<Item = Envelope>,
4648
) -> anyhow::Result<()> {
47-
let mut writer = std::fs::File::create(&output_file_name)
49+
let file = OpenOptions::new()
50+
.write(true)
51+
.create(true)
52+
.truncate(true)
53+
.open(output_file_name.as_path())
4854
.map(BufWriter::new)
49-
.with_context(|| format!("Failed to create output file: {:?}", &output_file_name))?;
50-
for envelope in envelopes {
51-
serde_json::to_writer(&mut writer, &envelope).with_context(|| {
52-
format!("Failed to write execution report: {:?}", &output_file_name)
53-
})?;
54-
// TODO: add a newline character to separate the entries
55-
}
55+
.with_context(|| format!("Failed to open file: {:?}", output_file_name))?;
56+
57+
write(file, envelopes)?;
58+
5659
Ok(())
5760
}
5861
}
@@ -64,11 +67,10 @@ impl Mode for Intercept {
6467
///
6568
/// The exit code is based on the result of the build command.
6669
fn run(self) -> anyhow::Result<ExitCode> {
67-
let output_file_name = self.output.file_name.clone();
68-
let service = CollectorService::new(move |envelopes| {
69-
Self::write_to_file(output_file_name, envelopes)
70-
})
71-
.with_context(|| "Failed to create the ipc service")?;
70+
let output_file = PathBuf::from(self.output.file_name);
71+
let service =
72+
CollectorService::new(move |envelopes| Self::write_to_file(output_file, envelopes))
73+
.with_context(|| "Failed to create the ipc service")?;
7274
let environment = InterceptEnvironment::new(&self.config, service.address())
7375
.with_context(|| "Failed to create the ipc environment")?;
7476

rust/bear/src/modes/semantic.rs

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22

33
use super::super::semantic;
4-
use crate::ipc::{Envelope, Execution};
54
use crate::modes::Mode;
5+
use crate::output::event::read;
66
use crate::output::{OutputWriter, OutputWriterImpl};
77
use crate::semantic::transformation::Transformation;
88
use crate::semantic::Transform;
99
use crate::{args, config};
1010
use anyhow::Context;
11-
use serde_json::de::IoRead;
12-
use serde_json::{Error, StreamDeserializer};
13-
use std::convert::TryFrom;
14-
use std::fs::OpenOptions;
11+
use std::fs::{File, OpenOptions};
1512
use std::io::BufReader;
16-
use std::path::PathBuf;
1713
use std::process::ExitCode;
1814

1915
/// The semantic mode we are deduct the semantic meaning of the
2016
/// executed commands from the build process.
2117
pub struct Semantic {
22-
event_source: EventFileReader,
18+
event_source: BufReader<File>,
2319
interpreter: Box<dyn semantic::Interpreter>,
2420
semantic_transform: Transformation,
2521
output_writer: OutputWriterImpl,
@@ -32,7 +28,7 @@ impl Semantic {
3228
output: args::BuildSemantic,
3329
config: config::Main,
3430
) -> anyhow::Result<Self> {
35-
let event_source = EventFileReader::try_from(input)?;
31+
let event_source = Self::open(input.file_name.as_str())?;
3632
let interpreter = Self::interpreter(&config)?;
3733
let semantic_transform = Transformation::from(&config.output);
3834
let output_writer = OutputWriterImpl::create(&output, &config.output)?;
@@ -45,6 +41,16 @@ impl Semantic {
4541
})
4642
}
4743

44+
/// Open the event file for reading.
45+
fn open(file_name: &str) -> anyhow::Result<BufReader<File>> {
46+
let file = OpenOptions::new()
47+
.read(true)
48+
.open(file_name)
49+
.map(BufReader::new)
50+
.with_context(|| format!("Failed to open file: {:?}", file_name))?;
51+
Ok(file)
52+
}
53+
4854
/// Creates an interpreter to recognize the compiler calls.
4955
///
5056
/// Using the configuration we can define which compilers to include and exclude.
@@ -83,9 +89,8 @@ impl Mode for Semantic {
8389
/// The exit code is based on the result of the output writer.
8490
fn run(self) -> anyhow::Result<ExitCode> {
8591
// Set up the pipeline of compilation database entries.
86-
let entries = self
87-
.event_source
88-
.generate()
92+
let entries = read(self.event_source)
93+
.map(|envelope| envelope.event.execution)
8994
.inspect(|execution| log::debug!("execution: {}", execution))
9095
.flat_map(|execution| self.interpreter.recognize(&execution))
9196
.inspect(|semantic| log::debug!("semantic: {:?}", semantic))
@@ -98,46 +103,3 @@ impl Mode for Semantic {
98103
}
99104
}
100105
}
101-
102-
/// Responsible for reading the build events from the intercept mode.
103-
///
104-
/// The file syntax is defined by the `events` module, and the parsing logic is implemented there.
105-
/// Here we only handle the file opening and the error handling.
106-
pub struct EventFileReader {
107-
stream: Box<dyn Iterator<Item = Result<Envelope, Error>>>,
108-
}
109-
110-
impl TryFrom<args::BuildEvents> for EventFileReader {
111-
type Error = anyhow::Error;
112-
113-
/// Open the file and create a new instance of the event file reader.
114-
///
115-
/// If the file cannot be opened, the error will be logged and escalated.
116-
fn try_from(value: args::BuildEvents) -> Result<Self, Self::Error> {
117-
let file_name = PathBuf::from(value.file_name);
118-
let file = OpenOptions::new()
119-
.read(true)
120-
.open(file_name.as_path())
121-
.map(BufReader::new)
122-
.with_context(|| format!("Failed to open input file: {:?}", file_name))?;
123-
let stream = Box::new(StreamDeserializer::new(IoRead::new(file)));
124-
125-
Ok(EventFileReader { stream })
126-
}
127-
}
128-
129-
impl EventFileReader {
130-
/// Generate the build events from the file.
131-
///
132-
/// Returns an iterator over the build events. Any error during the reading
133-
/// of the file will be logged and the failed entries will be skipped.
134-
pub fn generate(self) -> impl Iterator<Item = Execution> {
135-
self.stream.filter_map(|result| match result {
136-
Ok(value) => Some(value.event.execution),
137-
Err(error) => {
138-
log::error!("Failed to read event: {:?}", error);
139-
None
140-
}
141-
})
142-
}
143-
}

rust/bear/src/output/event/mod.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
use crate::ipc::Envelope;
4+
use serde_json::de::IoRead;
5+
use serde_json::StreamDeserializer;
6+
use std::io;
7+
8+
/// Generate the build events from the file.
9+
///
10+
/// Returns an iterator over the build events.
11+
/// Any error will interrupt the reading process and the remaining events will be lost.
12+
pub fn read(reader: impl io::Read) -> impl Iterator<Item = Envelope> {
13+
let stream = StreamDeserializer::new(IoRead::new(reader));
14+
stream.filter_map(|result| match result {
15+
Ok(value) => Some(value),
16+
Err(error) => {
17+
log::error!("Failed to read event: {:?}", error);
18+
None
19+
}
20+
})
21+
}
22+
23+
/// Write the build events to the file.
24+
///
25+
/// Can fail if the events cannot be serialized or written to the file.
26+
/// Any error will interrupt the writing process and the file will be incomplete.
27+
pub fn write(
28+
mut writer: impl io::Write,
29+
envelopes: impl IntoIterator<Item = Envelope>,
30+
) -> Result<(), anyhow::Error> {
31+
for envelope in envelopes {
32+
serde_json::to_writer(&mut writer, &envelope)?;
33+
writer.write_all(b"\n")?;
34+
}
35+
Ok(())
36+
}
37+
38+
#[cfg(test)]
39+
mod test {
40+
use super::*;
41+
use crate::ipc::{Event, Execution, ProcessId, ReporterId};
42+
use crate::vec_of_strings;
43+
use serde_json::json;
44+
use std::collections::HashMap;
45+
use std::io::Cursor;
46+
use std::path::PathBuf;
47+
48+
#[test]
49+
fn read_write() {
50+
let events = expected_values();
51+
52+
let mut buffer = Vec::new();
53+
write(&mut buffer, events.iter().cloned()).unwrap();
54+
let mut cursor = Cursor::new(buffer);
55+
let read_events: Vec<_> = read(&mut cursor).collect();
56+
57+
assert_eq!(events, read_events);
58+
}
59+
60+
#[test]
61+
fn read_write_empty() {
62+
let events = Vec::<Envelope>::new();
63+
64+
let mut buffer = Vec::new();
65+
write(&mut buffer, events.iter().cloned()).unwrap();
66+
let mut cursor = Cursor::new(buffer);
67+
let read_events: Vec<_> = read(&mut cursor).collect();
68+
69+
assert_eq!(events, read_events);
70+
}
71+
72+
#[test]
73+
fn read_stops_on_errors() {
74+
let line1 = json!({
75+
"rid": 42,
76+
"timestamp": 0,
77+
"event": {
78+
"pid": 11782,
79+
"execution": {
80+
"executable": "/usr/bin/clang",
81+
"arguments": ["clang", "-c", "main.c"],
82+
"working_dir": "/home/user",
83+
"environment": {
84+
"PATH": "/usr/bin",
85+
"HOME": "/home/user"
86+
}
87+
}
88+
}
89+
});
90+
let line2 = json!({"rid": 42 });
91+
let line3 = json!({
92+
"rid": 42,
93+
"timestamp": 273,
94+
"event": {
95+
"pid": 11934,
96+
"execution": {
97+
"executable": "/usr/bin/clang",
98+
"arguments": ["clang", "-c", "output.c"],
99+
"working_dir": "/home/user",
100+
"environment": {}
101+
}
102+
}
103+
});
104+
let content = format!("{}\n{}\n{}\n", line1, line2, line3);
105+
106+
let mut cursor = Cursor::new(content);
107+
let read_events: Vec<_> = read(&mut cursor).collect();
108+
109+
// Only the fist event is read, all other lines are ignored.
110+
assert_eq!(expected_values()[0..1], read_events);
111+
}
112+
113+
const REPORTER_ID: ReporterId = ReporterId(42);
114+
115+
fn expected_values() -> Vec<Envelope> {
116+
vec![
117+
Envelope {
118+
rid: REPORTER_ID,
119+
timestamp: 0,
120+
event: Event {
121+
pid: ProcessId(11782),
122+
execution: Execution {
123+
executable: PathBuf::from("/usr/bin/clang"),
124+
arguments: vec_of_strings!["clang", "-c", "main.c"],
125+
working_dir: PathBuf::from("/home/user"),
126+
environment: HashMap::from([
127+
("PATH".to_string(), "/usr/bin".to_string()),
128+
("HOME".to_string(), "/home/user".to_string()),
129+
]),
130+
},
131+
},
132+
},
133+
Envelope {
134+
rid: REPORTER_ID,
135+
timestamp: 273,
136+
event: Event {
137+
pid: ProcessId(11934),
138+
execution: Execution {
139+
executable: PathBuf::from("/usr/bin/clang"),
140+
arguments: vec_of_strings!["clang", "-c", "output.c"],
141+
working_dir: PathBuf::from("/home/user"),
142+
environment: HashMap::from([]),
143+
},
144+
},
145+
},
146+
]
147+
}
148+
}

rust/bear/src/output/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use anyhow::{anyhow, Context, Result};
99
use path_absolutize::Absolutize;
1010

1111
mod clang;
12+
pub mod event;
1213
mod filter;
1314

1415
/// The output writer trait is responsible for writing output file.
@@ -69,7 +70,7 @@ pub(crate) struct SemanticOutputWriter {
6970
impl OutputWriter for SemanticOutputWriter {
7071
fn run(&self, entries: impl Iterator<Item = semantic::CompilerCall>) -> Result<()> {
7172
let file_name = &self.output;
72-
let file = File::create(&file_name)
73+
let file = File::create(file_name)
7374
.map(BufWriter::new)
7475
.with_context(|| format!("Failed to create file: {:?}", file_name.as_path()))?;
7576

0 commit comments

Comments
 (0)