include_graph/dependencies/
compiledb.rs

1use 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    /// everything relative to this directory
18    pub directory: String,
19
20    /// what file this compiles
21    pub file: String,
22
23    /// command as a string only (needs split)
24    pub command: Option<String>,
25
26    /// split-out arguments for compilation
27    pub arguments: Option<Vec<String>>,
28
29    /// Optional what gets outputted
30    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        // trace!("Generating SourceFileEntry {:#?}", value);
38
39        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}