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}