Skip to content

Commit 1e78597

Browse files
committed
rust: clang related output are private to the module
1 parent 3b6effb commit 1e78597

File tree

4 files changed

+189
-210
lines changed

4 files changed

+189
-210
lines changed

rust/bear/src/output/filter_duplicates.rs renamed to rust/bear/src/output/clang/filter_duplicates.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use std::collections::HashSet;
99
use std::hash::{DefaultHasher, Hash, Hasher};
1010

11-
use super::clang::Entry;
11+
use super::Entry;
1212
use crate::config;
1313
use thiserror::Error;
1414

@@ -77,8 +77,8 @@ impl TryFrom<config::DuplicateFilter> for DuplicateFilter {
7777

7878
#[cfg(test)]
7979
mod tests {
80+
use super::super::entry;
8081
use super::*;
81-
use std::path::PathBuf;
8282

8383
#[test]
8484
fn test_try_from_success() {
@@ -208,13 +208,4 @@ mod tests {
208208
assert!(sut.unique(&entry1));
209209
assert!(!sut.unique(&entry2));
210210
}
211-
212-
fn entry(file: &str, arguments: Vec<&str>, directory: &str, output: Option<&str>) -> Entry {
213-
Entry {
214-
file: PathBuf::from(file),
215-
arguments: arguments.into_iter().map(String::from).collect(),
216-
directory: PathBuf::from(directory),
217-
output: output.map(PathBuf::from),
218-
}
219-
}
220211
}

rust/bear/src/output/formatter.rs renamed to rust/bear/src/output/clang/formatter.rs

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22

3-
use crate::output::clang::Entry;
3+
use super::Entry;
44
use crate::semantic;
55
use anyhow::anyhow;
66
use std::path::{Path, PathBuf};
@@ -95,6 +95,7 @@ fn into_string(path: &Path) -> anyhow::Result<String> {
9595

9696
#[cfg(test)]
9797
mod test {
98+
use super::super::entry;
9899
use super::*;
99100

100101
#[test]
@@ -127,15 +128,12 @@ mod test {
127128
let sut = EntryFormatter::new();
128129
let result = sut.apply(input);
129130

130-
let expected = vec![Entry {
131-
directory: "/home/user".into(),
132-
file: "source.c".into(),
133-
arguments: vec!["/usr/bin/clang", "-Wall", "-o", "source.o", "source.c"]
134-
.into_iter()
135-
.map(String::from)
136-
.collect(),
137-
output: Some("source.o".into()),
138-
}];
131+
let expected = vec![entry(
132+
"source.c",
133+
vec!["/usr/bin/clang", "-Wall", "-o", "source.o", "source.c"],
134+
"/home/user",
135+
Some("source.o"),
136+
)];
139137
assert_eq!(expected, result);
140138
}
141139

@@ -163,24 +161,18 @@ mod test {
163161
let result = sut.apply(input);
164162

165163
let expected = vec![
166-
Entry {
167-
directory: "/home/user".into(),
168-
file: "/tmp/source1.c".into(),
169-
arguments: vec!["clang", "-o", "./source1.o", "/tmp/source1.c"]
170-
.into_iter()
171-
.map(String::from)
172-
.collect(),
173-
output: Some("./source1.o".into()),
174-
},
175-
Entry {
176-
directory: "/home/user".into(),
177-
file: "../source2.c".into(),
178-
arguments: vec!["clang", "-Wall", "../source2.c"]
179-
.into_iter()
180-
.map(String::from)
181-
.collect(),
182-
output: None,
183-
},
164+
entry(
165+
"/tmp/source1.c",
166+
vec!["clang", "-o", "./source1.o", "/tmp/source1.c"],
167+
"/home/user",
168+
Some("./source1.o"),
169+
),
170+
entry(
171+
"../source2.c",
172+
vec!["clang", "-Wall", "../source2.c"],
173+
"/home/user",
174+
None,
175+
),
184176
];
185177
assert_eq!(expected, result);
186178
}

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

Lines changed: 163 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,53 +11,197 @@
1111
//! as an array. The definition of the JSON compilation database files is done in the
1212
//! LLVM project [documentation](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
1313
14-
use crate::output::json;
15-
use serde::Serialize;
16-
use serde_json::Error;
14+
mod filter_duplicates;
15+
mod formatter;
1716
mod type_de;
1817

18+
use super::formats::{FileFormat, JsonCompilationDatabase};
19+
use super::IteratorWriter;
20+
use crate::{config, semantic};
21+
use anyhow::Context;
22+
use serde::Serialize;
23+
use std::{fs, io, path};
24+
1925
/// Represents an entry of the compilation database.
2026
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2127
pub struct Entry {
2228
/// The main translation unit source processed by this compilation step.
2329
/// This is used by tools as the key into the compilation database.
2430
/// There can be multiple command objects for the same file, for example if the same
2531
/// source file is compiled with different configurations.
26-
pub file: std::path::PathBuf,
32+
pub file: path::PathBuf,
2733
/// The compile command executed. This must be a valid command to rerun the exact
2834
/// compilation step for the translation unit in the environment the build system uses.
2935
/// Shell expansion is not supported.
3036
pub arguments: Vec<String>,
3137
/// The working directory of the compilation. All paths specified in the command or
3238
/// file fields must be either absolute or relative to this directory.
33-
pub directory: std::path::PathBuf,
39+
pub directory: path::PathBuf,
3440
/// The name of the output created by this compilation step. This field is optional.
3541
/// It can be used to distinguish different processing modes of the same input file.
3642
#[serde(skip_serializing_if = "Option::is_none")]
37-
pub output: Option<std::path::PathBuf>,
43+
pub output: Option<path::PathBuf>,
3844
}
3945

4046
#[cfg(test)]
4147
pub fn entry(file: &str, arguments: Vec<&str>, directory: &str, output: Option<&str>) -> Entry {
4248
Entry {
43-
file: std::path::PathBuf::from(file),
49+
file: path::PathBuf::from(file),
4450
arguments: arguments.into_iter().map(String::from).collect(),
45-
directory: std::path::PathBuf::from(directory),
46-
output: output.map(std::path::PathBuf::from),
51+
directory: path::PathBuf::from(directory),
52+
output: output.map(path::PathBuf::from),
53+
}
54+
}
55+
56+
/// Formats `semantic::CompilerCall` instances into `Entry` objects.
57+
pub(super) struct FormattedClangOutputWriter<T: IteratorWriter<Entry>> {
58+
formatter: formatter::EntryFormatter,
59+
writer: T,
60+
}
61+
62+
impl<T: IteratorWriter<Entry>> FormattedClangOutputWriter<T> {
63+
pub(super) fn new(writer: T) -> Self {
64+
let formatter = formatter::EntryFormatter::new();
65+
Self { formatter, writer }
66+
}
67+
}
68+
69+
impl<T: IteratorWriter<Entry>> IteratorWriter<semantic::CompilerCall>
70+
for FormattedClangOutputWriter<T>
71+
{
72+
fn write(self, semantics: impl Iterator<Item = semantic::CompilerCall>) -> anyhow::Result<()> {
73+
let entries = semantics.flat_map(|semantic| self.formatter.apply(semantic));
74+
self.writer.write(entries)
75+
}
76+
}
77+
78+
/// Handles the logic for appending entries to an existing Clang output file.
79+
///
80+
/// This writer supports reading existing entries from a compilation database file,
81+
/// combining them with new entries, and writing the result back to the file.
82+
/// If the file does not exist and the append option is enabled, it logs a warning
83+
/// and writes only the new entries.
84+
pub(super) struct AppendClangOutputWriter<T: IteratorWriter<Entry>> {
85+
writer: T,
86+
path: Option<path::PathBuf>,
87+
}
88+
89+
impl<T: IteratorWriter<Entry>> AppendClangOutputWriter<T> {
90+
pub(super) fn new(writer: T, append: bool, file_name: &path::Path) -> Self {
91+
let path = if file_name.exists() {
92+
Some(file_name.to_path_buf())
93+
} else {
94+
if append {
95+
log::warn!("The output file does not exist, the append option is ignored.");
96+
}
97+
None
98+
};
99+
Self { writer, path }
100+
}
101+
102+
/// Reads the compilation database from a file.
103+
///
104+
/// NOTE: The function is intentionally not getting any `&self` reference,
105+
/// because the logic is not bound to the instance.
106+
fn read_from_compilation_db(
107+
source: &path::Path,
108+
) -> anyhow::Result<impl Iterator<Item = Entry>> {
109+
let source_copy = source.to_path_buf();
110+
111+
let file = fs::File::open(source)
112+
.map(io::BufReader::new)
113+
.with_context(|| format!("Failed to open file: {:?}", source))?;
114+
115+
let entries = JsonCompilationDatabase::read_and_ignore(file, source_copy);
116+
Ok(entries)
117+
}
118+
}
119+
120+
impl<T: IteratorWriter<Entry>> IteratorWriter<Entry> for AppendClangOutputWriter<T> {
121+
fn write(self, entries: impl Iterator<Item = Entry>) -> anyhow::Result<()> {
122+
if let Some(path) = self.path {
123+
let entries_from_db = Self::read_from_compilation_db(&path)?;
124+
let final_entries = entries_from_db.chain(entries);
125+
self.writer.write(final_entries)
126+
} else {
127+
self.writer.write(entries)
128+
}
47129
}
48130
}
49131

50-
/// Deserialize entries from a JSON array into an iterator.
51-
pub fn read(reader: impl std::io::Read) -> impl Iterator<Item = Result<Entry, Error>> {
52-
json::read_array(reader)
132+
/// Responsible for writing a JSON compilation database file atomically.
133+
///
134+
/// The file is first written to a temporary file and then renamed to the final file name.
135+
/// This ensures that the output file is not left in an inconsistent state in case of errors.
136+
pub(super) struct AtomicClangOutputWriter<T: IteratorWriter<Entry>> {
137+
writer: T,
138+
temp_file_name: path::PathBuf,
139+
final_file_name: path::PathBuf,
140+
}
141+
142+
impl<T: IteratorWriter<Entry>> AtomicClangOutputWriter<T> {
143+
pub(super) fn new(
144+
writer: T,
145+
temp_file_name: &path::Path,
146+
final_file_name: &path::Path,
147+
) -> Self {
148+
Self {
149+
writer,
150+
temp_file_name: temp_file_name.to_path_buf(),
151+
final_file_name: final_file_name.to_path_buf(),
152+
}
153+
}
154+
}
155+
156+
impl<T: IteratorWriter<Entry>> IteratorWriter<Entry> for AtomicClangOutputWriter<T> {
157+
fn write(self, entries: impl Iterator<Item = Entry>) -> anyhow::Result<()> {
158+
let temp_file_name = self.temp_file_name.clone();
159+
let final_file_name = self.final_file_name.clone();
160+
161+
self.writer.write(entries)?;
162+
163+
fs::rename(&temp_file_name, &final_file_name).with_context(|| {
164+
format!(
165+
"Failed to rename file from '{:?}' to '{:?}'.",
166+
temp_file_name, final_file_name
167+
)
168+
})?;
169+
170+
Ok(())
171+
}
53172
}
54173

55-
/// Serialize entries from an iterator into a JSON array.
174+
/// Responsible for writing a JSON compilation database file from the given entries.
56175
///
57-
/// It uses the `arguments` field of the `Entry` struct to serialize the array of strings.
58-
pub fn write(
59-
writer: impl std::io::Write,
60-
entries: impl Iterator<Item = Entry>,
61-
) -> Result<(), Error> {
62-
json::write_array(writer, entries)
176+
/// # Features
177+
/// - Writes the entries to a file.
178+
/// - Filters duplicates based on the provided configuration.
179+
pub(super) struct ClangOutputWriter {
180+
output: io::BufWriter<fs::File>,
181+
filter: filter_duplicates::DuplicateFilter,
182+
}
183+
184+
impl TryFrom<(&path::Path, &config::DuplicateFilter)> for ClangOutputWriter {
185+
type Error = anyhow::Error;
186+
187+
fn try_from(value: (&path::Path, &config::DuplicateFilter)) -> Result<Self, Self::Error> {
188+
let (file_name, config) = value;
189+
190+
let output = fs::File::create(file_name)
191+
.map(io::BufWriter::new)
192+
.with_context(|| format!("Failed to open file: {:?}", file_name))?;
193+
194+
let filter = filter_duplicates::DuplicateFilter::try_from(config.clone())?;
195+
196+
Ok(Self { output, filter })
197+
}
198+
}
199+
200+
impl IteratorWriter<Entry> for ClangOutputWriter {
201+
fn write(self, entries: impl Iterator<Item = Entry>) -> anyhow::Result<()> {
202+
let mut filter = self.filter.clone();
203+
let filtered_entries = entries.filter(move |entry| filter.unique(entry));
204+
JsonCompilationDatabase::write(self.output, filtered_entries)?;
205+
Ok(())
206+
}
63207
}

0 commit comments

Comments
 (0)