1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

use serde::Deserialize;
use tokio::process::Command;
use tracing::{error, info};

use super::error::Error;

#[derive(Debug, PartialEq)]
pub struct GnTarget {
    pub name: String,
    pub sources: Vec<PathBuf>,
}

#[derive(Deserialize)]
#[serde(transparent)]
struct GnSourcesOutput {
    inner: HashMap<String, SourcesData>,
}

#[derive(Deserialize)]
struct SourcesData {
    sources: Option<Vec<String>>,
}

pub async fn load_gn_targets(
    gn_dir: &Path,
    source_root: &Path,
    target: &str,
) -> Result<Vec<GnTarget>, Error> {
    // TODO: GN PATH?
    let mut command = Command::new("/usr/bin/gn");
    command.arg("desc");
    command.arg("--format=json");
    command.arg(format!(
        "--root={}",
        source_root
            .canonicalize()
            .map_err(|e| Error::Internal {
                message: format!("Canonical path: {:?}", e),
            })?
            .to_string_lossy(),
    ));
    command.arg(gn_dir.canonicalize().map_err(|e| Error::Internal {
        message: format!("Canonical path: {:?}", e),
    })?);
    command.arg(target);
    command.arg("sources");

    let output = command.output().await.map_err(|e| Error::Internal {
        message: format!("Canonical path: {:?}", e),
    })?;

    if !output.status.success() {
        let data = String::from_utf8_lossy(&output.stdout);
        if data.len() > 0 {
            for l in data.lines() {
                error!("STDOUT: {}", l);
            }
        }

        let data = String::from_utf8_lossy(&output.stderr);
        if data.len() > 0 {
            for l in data.lines() {
                error!("STDERR: {}", l);
            }
        }

        return Err(Error::Internal {
            message: format!("Failed to execute GN. Status {:?}.", output.status),
        });
    }

    let decoded: GnSourcesOutput =
        serde_json::from_slice(&output.stdout).map_err(|e| Error::Internal {
            message: format!("JSON parse error: {:?}", e),
        })?;

    // filter-map because not all targets have sources. However the ones that do have
    // can be recognized
    Ok(decoded
        .inner
        .into_iter()
        .filter_map(|(name, sources)| {
            info!(target: "gn-path", "Sources for {}", &name);
            sources.sources.map(|sources| GnTarget {
                name,
                sources: sources
                    .into_iter()
                    .filter_map(|s| {
                        if s.starts_with("//") {
                            // paths starting with // are relative to the source root
                            let mut path = PathBuf::from(source_root);
                            path.push(PathBuf::from(&s.as_str()[2..]));
                            path
                        } else {
                            // otherwise assume absolute and use as-is
                            PathBuf::from(&s.as_str())
                        }
                        .canonicalize()
                        .ok()
                    })
                    .inspect(|path| {
                        info!(target: "gn-path", " - {:?}", path);
                    })
                    .collect(),
            })
        })
        .filter(|t| !t.sources.is_empty())
        .collect())
}