|
11 | 11 | //! as an array. The definition of the JSON compilation database files is done in the
|
12 | 12 | //! LLVM project [documentation](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
|
13 | 13 |
|
14 |
| -use crate::output::json; |
15 |
| -use serde::Serialize; |
16 |
| -use serde_json::Error; |
| 14 | +mod filter_duplicates; |
| 15 | +mod formatter; |
17 | 16 | mod type_de;
|
18 | 17 |
|
| 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 | + |
19 | 25 | /// Represents an entry of the compilation database.
|
20 | 26 | #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
21 | 27 | pub struct Entry {
|
22 | 28 | /// The main translation unit source processed by this compilation step.
|
23 | 29 | /// This is used by tools as the key into the compilation database.
|
24 | 30 | /// There can be multiple command objects for the same file, for example if the same
|
25 | 31 | /// source file is compiled with different configurations.
|
26 |
| - pub file: std::path::PathBuf, |
| 32 | + pub file: path::PathBuf, |
27 | 33 | /// The compile command executed. This must be a valid command to rerun the exact
|
28 | 34 | /// compilation step for the translation unit in the environment the build system uses.
|
29 | 35 | /// Shell expansion is not supported.
|
30 | 36 | pub arguments: Vec<String>,
|
31 | 37 | /// The working directory of the compilation. All paths specified in the command or
|
32 | 38 | /// file fields must be either absolute or relative to this directory.
|
33 |
| - pub directory: std::path::PathBuf, |
| 39 | + pub directory: path::PathBuf, |
34 | 40 | /// The name of the output created by this compilation step. This field is optional.
|
35 | 41 | /// It can be used to distinguish different processing modes of the same input file.
|
36 | 42 | #[serde(skip_serializing_if = "Option::is_none")]
|
37 |
| - pub output: Option<std::path::PathBuf>, |
| 43 | + pub output: Option<path::PathBuf>, |
38 | 44 | }
|
39 | 45 |
|
40 | 46 | #[cfg(test)]
|
41 | 47 | pub fn entry(file: &str, arguments: Vec<&str>, directory: &str, output: Option<&str>) -> Entry {
|
42 | 48 | Entry {
|
43 |
| - file: std::path::PathBuf::from(file), |
| 49 | + file: path::PathBuf::from(file), |
44 | 50 | 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 | + } |
47 | 129 | }
|
48 | 130 | }
|
49 | 131 |
|
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 | + } |
53 | 172 | }
|
54 | 173 |
|
55 |
| -/// Serialize entries from an iterator into a JSON array. |
| 174 | +/// Responsible for writing a JSON compilation database file from the given entries. |
56 | 175 | ///
|
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 | + } |
63 | 207 | }
|
0 commit comments