Skip to content

Commit 9e55969

Browse files
committed
rust: clang output writer simplified
1 parent 9c27f12 commit 9e55969

File tree

2 files changed

+317
-190
lines changed

2 files changed

+317
-190
lines changed

rust/bear/src/modes/semantic.rs

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

33
use crate::intercept::Event;
4-
use crate::output::OutputWriter;
4+
use crate::output::IteratorWriter;
55
use crate::semantic::interpreters;
66
use crate::semantic::transformation::FilterAndFormat;
77
use crate::semantic::{transformation, Recognition};
88
use crate::{args, config, output, semantic};
9-
use anyhow::Context;
10-
use std::fs::{File, OpenOptions};
11-
use std::io::{BufReader, BufWriter};
12-
use std::path::{Path, PathBuf};
139

14-
/// The semantic analysis that is independent of the event source.
15-
pub(super) struct SemanticAnalysisPipeline {
10+
pub(super) struct SemanticAnalysis {
1611
interpreter: Box<dyn semantic::Interpreter>,
1712
transformation: Box<dyn transformation::Transformation>,
18-
output_writer: OutputWriterImpl,
1913
}
2014

21-
impl SemanticAnalysisPipeline {
22-
/// Create a new semantic mode instance.
23-
pub(super) fn create(
24-
output: args::BuildSemantic,
25-
config: &config::Main,
26-
) -> anyhow::Result<Self> {
15+
impl TryFrom<&config::Main> for SemanticAnalysis {
16+
type Error = anyhow::Error;
17+
18+
fn try_from(config: &config::Main) -> Result<Self, Self::Error> {
2719
let interpreter = interpreters::create(config);
2820
let transformation = FilterAndFormat::try_from(&config.output)?;
29-
let output_writer = OutputWriterImpl::create(&output, &config.output)?;
3021

3122
Ok(Self {
3223
interpreter: Box::new(interpreter),
3324
transformation: Box::new(transformation),
34-
output_writer,
3525
})
3626
}
27+
}
3728

38-
/// Consumer the envelopes for analysis and write the result to the output file.
39-
/// This implements the pipeline of the semantic analysis.
40-
pub(super) fn analyze_and_write(
41-
self,
42-
events: impl IntoIterator<Item = Event>,
43-
) -> anyhow::Result<()> {
44-
// Set up the pipeline of compilation database entries.
45-
let semantics = events.into_iter().flat_map(|event| self.analyze(event));
46-
// Consume the entries and write them to the output file.
47-
// The exit code is based on the result of the output writer.
48-
self.output_writer.run(semantics)
49-
}
50-
51-
pub(super) fn analyze(&self, event: Event) -> Option<semantic::CompilerCall> {
29+
impl SemanticAnalysis {
30+
pub fn analyze(&self, event: Event) -> Option<semantic::CompilerCall> {
5231
log::debug!("event: {}", event);
5332
match self.interpreter.recognize(&event.execution) {
5433
Recognition::Success(recognized) => {
@@ -89,164 +68,36 @@ impl SemanticAnalysisPipeline {
8968
}
9069
}
9170

92-
/// The output writer implementation.
93-
///
94-
/// This is a workaround for the lack of trait object support for generic arguments.
95-
/// https://doc.rust-lang.org/reference/items/traits.html#object-safety.
96-
pub(crate) enum OutputWriterImpl {
97-
Clang(ClangOutputWriter),
98-
Semantic(SemanticOutputWriter),
99-
}
100-
101-
impl OutputWriter for OutputWriterImpl {
102-
fn run(&self, semantics: impl Iterator<Item = semantic::CompilerCall>) -> anyhow::Result<()> {
103-
match self {
104-
OutputWriterImpl::Clang(writer) => writer.run(semantics),
105-
OutputWriterImpl::Semantic(writer) => writer.run(semantics),
106-
}
107-
}
108-
}
109-
110-
impl OutputWriterImpl {
111-
/// Create a new instance of the output writer.
112-
pub(crate) fn create(
113-
args: &args::BuildSemantic,
114-
config: &config::Output,
115-
) -> anyhow::Result<OutputWriterImpl> {
116-
// TODO: This method should fail early if the output file is not writable.
117-
match config {
118-
config::Output::Clang { duplicates, .. } => {
119-
let result = ClangOutputWriter {
120-
output: PathBuf::from(&args.file_name),
121-
append: args.append,
122-
formatter: output::formatter::EntryFormatter::new(),
123-
filter: output::filter_duplicates::DuplicateFilter::try_from(
124-
duplicates.clone(),
125-
)?,
126-
};
127-
Ok(OutputWriterImpl::Clang(result))
128-
}
129-
config::Output::Semantic { .. } => {
130-
let result = SemanticOutputWriter {
131-
output: PathBuf::from(&args.file_name),
132-
};
133-
Ok(OutputWriterImpl::Semantic(result))
134-
}
135-
}
136-
}
137-
}
138-
139-
pub(crate) struct SemanticOutputWriter {
140-
output: PathBuf,
141-
}
142-
143-
impl OutputWriter for SemanticOutputWriter {
144-
fn run(&self, semantics: impl Iterator<Item = semantic::CompilerCall>) -> anyhow::Result<()> {
145-
let file_name = &self.output;
146-
let file = File::create(file_name)
147-
.map(BufWriter::new)
148-
.with_context(|| format!("Failed to create file: {:?}", file_name.as_path()))?;
149-
150-
semantic::serialize(file, semantics)?;
151-
152-
Ok(())
153-
}
71+
/// The semantic analysis that is independent of the event source.
72+
pub(super) struct SemanticAnalysisPipeline {
73+
analyzer: SemanticAnalysis,
74+
writer: output::OutputWriter,
15475
}
15576

156-
/// Responsible for writing the final compilation database file.
157-
///
158-
/// Implements filtering, formatting and atomic file writing.
159-
/// (Atomic file writing implemented by writing to a temporary file and renaming it.)
160-
///
161-
/// Filtering is implemented by the `filter` module, and the formatting is implemented by the
162-
/// `json_compilation_db` module.
163-
pub(crate) struct ClangOutputWriter {
164-
output: PathBuf,
165-
append: bool,
166-
formatter: output::formatter::EntryFormatter,
167-
filter: output::filter_duplicates::DuplicateFilter,
168-
}
77+
impl SemanticAnalysisPipeline {
78+
/// Create a new semantic mode instance.
79+
pub(super) fn create(
80+
output: args::BuildSemantic,
81+
config: &config::Main,
82+
) -> anyhow::Result<Self> {
83+
let analyzer = SemanticAnalysis::try_from(config)?;
84+
let writer = output::OutputWriter::try_from((&output, &config.output))?;
16985

170-
impl OutputWriter for ClangOutputWriter {
171-
/// Implements the main logic of the output writer.
172-
fn run(&self, semantics: impl Iterator<Item = semantic::CompilerCall>) -> anyhow::Result<()> {
173-
let entries = semantics.flat_map(|semantic| self.formatter.apply(semantic));
174-
if self.append && self.output.exists() {
175-
let entries_from_db = Self::read_from_compilation_db(self.output.as_path())?;
176-
let final_entries = entries_from_db.chain(entries);
177-
self.write_into_compilation_db(final_entries)
178-
} else {
179-
if self.append {
180-
log::warn!("The output file does not exist, the append option is ignored.");
181-
}
182-
self.write_into_compilation_db(entries)
183-
}
86+
Ok(Self { analyzer, writer })
18487
}
185-
}
18688

187-
impl ClangOutputWriter {
188-
/// Write the entries to the compilation database.
189-
///
190-
/// The entries are written to a temporary file and then renamed to the final output.
191-
/// This ensures that the output file is always in a consistent state.
192-
fn write_into_compilation_db(
193-
&self,
194-
entries: impl Iterator<Item = output::clang::Entry>,
89+
/// Consumer the envelopes for analysis and write the result to the output file.
90+
/// This implements the pipeline of the semantic analysis.
91+
pub(super) fn analyze_and_write(
92+
self,
93+
events: impl IntoIterator<Item = Event>,
19594
) -> anyhow::Result<()> {
196-
// Filter out the entries as per the configuration.
197-
let mut filter = self.filter.clone();
198-
let filtered_entries = entries.filter(move |entry| filter.unique(entry));
199-
// Write the entries to a temporary file.
200-
self.write_into_temporary_compilation_db(filtered_entries)
201-
.and_then(|temp| {
202-
// Rename the temporary file to the final output.
203-
std::fs::rename(temp.as_path(), self.output.as_path()).with_context(|| {
204-
format!(
205-
"Failed to rename file from '{:?}' to '{:?}'.",
206-
temp.as_path(),
207-
self.output.as_path()
208-
)
209-
})
210-
})
211-
}
212-
213-
/// Write the entries to a temporary file and returns the temporary file name.
214-
fn write_into_temporary_compilation_db(
215-
&self,
216-
entries: impl Iterator<Item = output::clang::Entry>,
217-
) -> anyhow::Result<PathBuf> {
218-
// Generate a temporary file name.
219-
let file_name = self.output.with_extension("tmp");
220-
// Open the file for writing.
221-
let file = File::create(&file_name)
222-
.map(BufWriter::new)
223-
.with_context(|| format!("Failed to create file: {:?}", file_name.as_path()))?;
224-
// Write the entries to the file.
225-
output::clang::write(file, entries)
226-
.with_context(|| format!("Failed to write entries: {:?}", file_name.as_path()))?;
227-
// Return the temporary file name.
228-
Ok(file_name)
229-
}
230-
231-
/// Read the compilation database from a file.
232-
fn read_from_compilation_db(
233-
source: &Path,
234-
) -> anyhow::Result<impl Iterator<Item = output::clang::Entry>> {
235-
let source_copy = source.to_path_buf();
236-
237-
let file = OpenOptions::new()
238-
.read(true)
239-
.open(source)
240-
.map(BufReader::new)
241-
.with_context(|| format!("Failed to open file: {:?}", source))?;
242-
243-
let entries = output::clang::read(file).filter_map(move |candidate| match candidate {
244-
Ok(entry) => Some(entry),
245-
Err(error) => {
246-
log::error!("Failed to read file: {:?}, reason: {}", source_copy, error);
247-
None
248-
}
249-
});
250-
Ok(entries)
95+
// Set up the pipeline of compilation database entries.
96+
let semantics = events
97+
.into_iter()
98+
.flat_map(|event| self.analyzer.analyze(event));
99+
// Consume the entries and write them to the output file.
100+
// The exit code is based on the result of the output writer.
101+
self.writer.write(semantics)
251102
}
252103
}

0 commit comments

Comments
 (0)