tonic_buf_build/
buf.rs

1use std::ffi::OsStr;
2use std::path::{Path, PathBuf};
3
4use serde::Deserialize;
5
6use crate::error::TonicBufBuildError;
7
8#[derive(Debug, PartialEq, Deserialize)]
9pub(crate) struct BufYaml {
10    pub deps: Option<Vec<String>>,
11}
12
13impl BufYaml {
14    pub(crate) fn load(file: &Path) -> Result<BufYaml, TonicBufBuildError> {
15        let f = std::fs::File::open(file).map_err(|e| {
16            TonicBufBuildError::new(&format!("failed to read {:?}", file.as_os_str()), e.into())
17        })?;
18
19        let buf: BufYaml = serde_yaml::from_reader(&f).map_err(|e| {
20            TonicBufBuildError::new(
21                &format!("failed to deserialize {:?}", file.as_os_str()),
22                e.into(),
23            )
24        })?;
25        Ok(buf)
26    }
27}
28
29#[derive(Debug, PartialEq, Deserialize)]
30pub(crate) struct BufWorkYaml {
31    pub directories: Option<Vec<String>>,
32}
33
34impl BufWorkYaml {
35    pub(crate) fn load(file: &Path) -> Result<Self, TonicBufBuildError> {
36        let buf_work_file = std::fs::File::open(file).map_err(|e| {
37            TonicBufBuildError::new(&format!("failed to read {:?}", file.as_os_str()), e.into())
38        })?;
39
40        let buf_work: BufWorkYaml = serde_yaml::from_reader(&buf_work_file).map_err(|e| {
41            TonicBufBuildError::new(
42                &format!("failed to deserialize {:?}", file.as_os_str()),
43                e.into(),
44            )
45        })?;
46
47        Ok(buf_work)
48    }
49}
50
51pub(crate) fn ls_files(proto_path: &Path) -> Result<Vec<String>, TonicBufBuildError> {
52    let child = std::process::Command::new("buf")
53        .args([OsStr::new("ls-files"), proto_path.as_os_str()])
54        .output()
55        .map_err(|e| TonicBufBuildError::new("failed to execute `buf ls-files'", e.into()))?;
56
57    if !child.status.success() {
58        return Err(TonicBufBuildError::new_without_cause(&format!(
59            "failed to execute `buf ls-files', returned status code {}: {}",
60            child.status.code().unwrap_or(-1),
61            std::str::from_utf8(&child.stderr).unwrap()
62        )));
63    }
64    let protos = std::str::from_utf8(&child.stdout)
65        .map_err(|e| TonicBufBuildError::new("failed to decode `buf ls-files' output", e.into()))?
66        .trim_end()
67        .split('\n')
68        .map(|s| s.to_string())
69        .collect::<Vec<String>>();
70
71    Ok(protos)
72}
73
74pub(crate) fn export_all(buf: &BufYaml, export_dir: &Path) -> Result<(), TonicBufBuildError> {
75    let export_dir = export_dir.to_str().unwrap();
76
77    if let Some(deps) = &buf.deps {
78        for dep in deps {
79            std::process::Command::new("buf")
80                .args(["export", dep, "-o", export_dir])
81                .spawn()
82                .map_err(|e| {
83                    TonicBufBuildError::new(
84                        &format!("failed to execute `buf export {} -o {}'", &dep, &export_dir),
85                        e.into(),
86                    )
87                })?
88                .wait()
89                .map_err(|e| {
90                    TonicBufBuildError::new(
91                        &format!("failed to execute `buf export {} -o {}'", &dep, &export_dir),
92                        e.into(),
93                    )
94                })?;
95        }
96    }
97
98    Ok(())
99}
100
101pub(crate) fn export_all_from_workspace(
102    buf_work: &BufWorkYaml,
103    export_dir: &Path,
104    workspace_dir: &Path,
105) -> Result<Vec<PathBuf>, TonicBufBuildError> {
106    let mut buf_dirs = vec![];
107    if let Some(directories) = &buf_work.directories {
108        for dir in directories {
109            let mut buf_dir = PathBuf::from(workspace_dir);
110            buf_dir.push(dir);
111            buf_dirs.push(buf_dir.clone());
112            buf_dir.push("buf.yaml");
113
114            let buf = BufYaml::load(buf_dir.as_path())?;
115
116            export_all(&buf, export_dir)?;
117        }
118    }
119    Ok(buf_dirs)
120}