include_graph/dependencies/
gn.rs

1use std::{
2    collections::HashMap,
3    path::{Path, PathBuf},
4};
5
6use serde::Deserialize;
7use tokio::process::Command;
8use tracing::{error, info};
9
10use super::error::Error;
11
12#[derive(Debug, PartialEq)]
13pub struct GnTarget {
14    pub name: String,
15    pub sources: Vec<PathBuf>,
16}
17
18#[derive(Deserialize)]
19#[serde(transparent)]
20struct GnSourcesOutput {
21    inner: HashMap<String, SourcesData>,
22}
23
24#[derive(Deserialize)]
25struct SourcesData {
26    sources: Option<Vec<String>>,
27}
28
29pub async fn load_gn_targets(
30    gn_dir: &Path,
31    source_root: &Path,
32    target: &str,
33) -> Result<Vec<GnTarget>, Error> {
34    // TODO: GN PATH?
35    let mut command = Command::new("/usr/bin/gn");
36    command.arg("desc");
37    command.arg("--format=json");
38    command.arg(format!(
39        "--root={}",
40        source_root
41            .canonicalize()
42            .map_err(|e| Error::Internal {
43                message: format!("Canonical path: {:?}", e),
44            })?
45            .to_string_lossy(),
46    ));
47    command.arg(gn_dir.canonicalize().map_err(|e| Error::Internal {
48        message: format!("Canonical path: {:?}", e),
49    })?);
50    command.arg(target);
51    command.arg("sources");
52
53    let output = command.output().await.map_err(|e| Error::Internal {
54        message: format!("Canonical path: {:?}", e),
55    })?;
56
57    if !output.status.success() {
58        let data = String::from_utf8_lossy(&output.stdout);
59        if data.len() > 0 {
60            for l in data.lines() {
61                error!("STDOUT: {}", l);
62            }
63        }
64
65        let data = String::from_utf8_lossy(&output.stderr);
66        if data.len() > 0 {
67            for l in data.lines() {
68                error!("STDERR: {}", l);
69            }
70        }
71
72        return Err(Error::Internal {
73            message: format!("Failed to execute GN. Status {:?}.", output.status),
74        });
75    }
76
77    let decoded: GnSourcesOutput =
78        serde_json::from_slice(&output.stdout).map_err(|e| Error::Internal {
79            message: format!("JSON parse error: {:?}", e),
80        })?;
81
82    // filter-map because not all targets have sources. However the ones that do have
83    // can be recognized
84    Ok(decoded
85        .inner
86        .into_iter()
87        .filter_map(|(name, sources)| {
88            info!(target: "gn-path", "Sources for {}", &name);
89            sources.sources.map(|sources| GnTarget {
90                name,
91                sources: sources
92                    .into_iter()
93                    .filter_map(|s| {
94                        if s.starts_with("//") {
95                            // paths starting with // are relative to the source root
96                            let mut path = PathBuf::from(source_root);
97                            path.push(PathBuf::from(&s.as_str()[2..]));
98                            path
99                        } else {
100                            // otherwise assume absolute and use as-is
101                            PathBuf::from(&s.as_str())
102                        }
103                        .canonicalize()
104                        .ok()
105                    })
106                    .inspect(|path| {
107                        info!(target: "gn-path", " - {:?}", path);
108                    })
109                    .collect(),
110            })
111        })
112        .filter(|t| !t.sources.is_empty())
113        .collect())
114}