include_graph/dependencies/
compiledb.rs1use serde::{Deserialize, Serialize};
2use tokio::{fs::File, io::AsyncReadExt as _};
3use tracing::debug;
4
5use std::path::PathBuf;
6
7use crate::dependencies::error::Error;
8
9#[derive(Debug, PartialEq, PartialOrd, Hash, Serialize, Deserialize)]
10pub struct SourceFileEntry {
11 pub file_path: PathBuf,
12 pub include_directories: Vec<PathBuf>,
13}
14
15#[derive(Serialize, Deserialize, Debug)]
16pub struct CompileCommandsEntry {
17 pub directory: String,
19
20 pub file: String,
22
23 pub command: Option<String>,
25
26 pub arguments: Option<Vec<String>>,
28
29 pub output: Option<String>,
31}
32
33impl TryFrom<CompileCommandsEntry> for SourceFileEntry {
34 type Error = Error;
35
36 fn try_from(value: CompileCommandsEntry) -> Result<Self, Self::Error> {
37 let start_dir = PathBuf::from(value.directory);
40
41 let source_file = PathBuf::from(value.file);
42 let file_path = if source_file.is_relative() {
43 start_dir.join(source_file)
44 } else {
45 source_file
46 };
47
48 let file_path = file_path.canonicalize().map_err(|source| Error::IOError {
49 source,
50 path: file_path.clone(),
51 message: "canonicalize",
52 })?;
53
54 let args = value
55 .arguments
56 .unwrap_or_else(|| shlex::split(&value.command.unwrap()).unwrap());
57
58 let include_directories = args
59 .iter()
60 .filter_map(|a| a.strip_prefix("-I"))
61 .map(PathBuf::from)
62 .filter_map(|p| {
63 if p.is_relative() {
64 start_dir.join(p).canonicalize().ok()
65 } else {
66 Some(p)
67 }
68 })
69 .collect();
70
71 Ok(SourceFileEntry {
72 file_path,
73 include_directories,
74 })
75 }
76}
77
78pub async fn parse_compile_database(path: &str) -> Result<Vec<SourceFileEntry>, Error> {
79 let mut file = File::open(path).await.map_err(|source| Error::IOError {
80 source,
81 path: path.into(),
82 message: "open",
83 })?;
84 let mut json_string = String::new();
85
86 file.read_to_string(&mut json_string)
87 .await
88 .map_err(|source| Error::IOError {
89 source,
90 path: path.into(),
91 message: "read_to_string",
92 })?;
93
94 let raw_items: Vec<CompileCommandsEntry> =
95 serde_json::from_str(&json_string).map_err(Error::JsonParseError)?;
96
97 Ok(raw_items
98 .into_iter()
99 .filter(|e| {
100 e.file.ends_with(".cpp")
101 || e.file.ends_with(".cc")
102 || e.file.ends_with(".cxx")
103 || e.file.ends_with(".c")
104 || e.file.ends_with(".h")
105 || e.file.ends_with(".hpp")
106 })
107 .map(SourceFileEntry::try_from)
108 .inspect(|r| {
109 if let Err(e) = r {
110 debug!(target: "compile-db", "Failed to parse: {:?}", e);
111 }
112 })
113 .filter_map(|r| r.ok())
114 .collect())
115}