diff --git a/Cargo.toml b/Cargo.toml index 5171b3e..7878bb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonic-buf-build" -version = "0.2.0" +version = "0.2.1" edition = "2021" description = "A build helper that integrates buf.build to tonic-build." license = "MIT" diff --git a/README.md b/README.md index 548c086..4afaa76 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,23 @@ fn main() -> Result<(), tonic_buf_build::error::TonicBufBuildError> { To use buf workspaces, simply call `tonic_buf_build::compile_from_buf_workspace` instead. For complete and working examples, take a look at the examples folder. + +When the buf files are not located in the current directory, you can configure the *absolute* path to the directory, containing either `buf.yaml` or `buf.work.yaml`, and call the corresponding `tonic_buf_build::compile_from_buf_with_config` or `tonic_buf_build::compile_from_buf_workspace_with_config`. + +Consider the following build.rs where the buf workspace directory is located one level above the crate (a usual case for multilanguage clients with common protos) + +```rust +use std::env; +use std::path::PathBuf; + +fn main() -> Result<(), tonic_buf_build::error::TonicBufBuildError> { + let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + path.pop(); + tonic_buf_build::compile_from_buf_workspace_with_config( + tonic_build::configure(), + None, + tonic_buf_build::TonicBufConfig{buf_dir: Some(path)}, + )?; + Ok(()) +} +``` diff --git a/src/buf.rs b/src/buf.rs index 339c8cb..e6dbb13 100644 --- a/src/buf.rs +++ b/src/buf.rs @@ -1,4 +1,5 @@ -use std::path::Path; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; use serde::Deserialize; @@ -10,12 +11,16 @@ pub(crate) struct BufYaml { } impl BufYaml { - pub(crate) fn load(file: &str) -> Result { - let f = std::fs::File::open(file) - .map_err(|e| TonicBufBuildError::new(&format!("failed to read {}", file), e.into()))?; + pub(crate) fn load(file: &Path) -> Result { + let f = std::fs::File::open(file).map_err(|e| { + TonicBufBuildError::new(&format!("failed to read {:?}", file.as_os_str()), e.into()) + })?; let buf: BufYaml = serde_yaml::from_reader(&f).map_err(|e| { - TonicBufBuildError::new(&format!("failed to deserialize {}", file), e.into()) + TonicBufBuildError::new( + &format!("failed to deserialize {:?}", file.as_os_str()), + e.into(), + ) })?; Ok(buf) } @@ -27,21 +32,25 @@ pub(crate) struct BufWorkYaml { } impl BufWorkYaml { - pub(crate) fn load(file: &str) -> Result { - let buf_work_file = std::fs::File::open(file) - .map_err(|e| TonicBufBuildError::new(&format!("failed to read {}", file), e.into()))?; + pub(crate) fn load(file: &Path) -> Result { + let buf_work_file = std::fs::File::open(file).map_err(|e| { + TonicBufBuildError::new(&format!("failed to read {:?}", file.as_os_str()), e.into()) + })?; let buf_work: BufWorkYaml = serde_yaml::from_reader(&buf_work_file).map_err(|e| { - TonicBufBuildError::new(&format!("failed to deserialize {}", file), e.into()) + TonicBufBuildError::new( + &format!("failed to deserialize {:?}", file.as_os_str()), + e.into(), + ) })?; Ok(buf_work) } } -pub(crate) fn ls_files() -> Result, TonicBufBuildError> { +pub(crate) fn ls_files(proto_path: &Path) -> Result, TonicBufBuildError> { let child = std::process::Command::new("buf") - .args(["ls-files"]) + .args([OsStr::new("ls-files"), proto_path.as_os_str()]) .output() .map_err(|e| TonicBufBuildError::new("failed to execute `buf ls-files'", e.into()))?; @@ -92,12 +101,20 @@ pub(crate) fn export_all(buf: &BufYaml, export_dir: &Path) -> Result<(), TonicBu pub(crate) fn export_all_from_workspace( buf_work: &BufWorkYaml, export_dir: &Path, -) -> Result<(), TonicBufBuildError> { + workspace_dir: &Path, +) -> Result, TonicBufBuildError> { + let mut buf_dirs = vec![]; if let Some(directories) = &buf_work.directories { for dir in directories { - let buf = BufYaml::load(&format!("{}/buf.yaml", dir))?; + let mut buf_dir = PathBuf::from(workspace_dir); + buf_dir.push(dir); + buf_dirs.push(buf_dir.clone()); + buf_dir.push("buf.yaml"); + + let buf = BufYaml::load(buf_dir.as_path())?; + export_all(&buf, export_dir)?; } } - Ok(()) + Ok(buf_dirs) } diff --git a/src/lib.rs b/src/lib.rs index 9fea4c3..fb2597c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,37 +28,60 @@ use error::TonicBufBuildError; use scopeguard::defer; +use std::path::{Path, PathBuf}; mod buf; pub mod error; -fn tempdir() -> std::path::PathBuf { +fn tempdir() -> PathBuf { let mut temp_dir = std::env::temp_dir(); temp_dir.push(uuid::Uuid::new_v4().to_string()); temp_dir } +#[derive(Default)] +pub struct TonicBufConfig = &'static str> { + pub buf_dir: Option

, +} + pub fn compile_from_buf_workspace( tonic_builder: tonic_build::Builder, config: Option, +) -> Result<(), TonicBufBuildError> { + compile_from_buf_workspace_with_config::<&'static str>( + tonic_builder, + config, + TonicBufConfig::default(), + ) +} + +pub fn compile_from_buf_workspace_with_config>( + tonic_builder: tonic_build::Builder, + config: Option, + tonic_buf_config: TonicBufConfig

, ) -> Result<(), TonicBufBuildError> { let export_dir = tempdir(); defer! { // This is just cleanup, it's not important if it fails let _ = std::fs::remove_dir(&export_dir); } + let buf_dir = tonic_buf_config + .buf_dir + .as_ref() + .map(|p| p.as_ref()) + .unwrap_or(".".as_ref()); + let mut buf_work_file = PathBuf::from(buf_dir); + buf_work_file.push("buf.work.yaml"); + let buf_work = buf::BufWorkYaml::load(buf_work_file.as_path())?; - let buf_work = buf::BufWorkYaml::load("buf.work.yaml")?; - - buf::export_all_from_workspace(&buf_work, &export_dir)?; - let buf_work_directories = buf_work.directories.unwrap_or_default(); - let mut includes = vec![export_dir.to_str().unwrap().to_string()]; + let buf_work_directories = buf::export_all_from_workspace(&buf_work, &export_dir, buf_dir)?; + let mut includes = vec![export_dir.clone()]; for dep in buf_work_directories { includes.push(dep); } - let protos = buf::ls_files()?; + let protos = buf::ls_files(buf_dir)?; match config { None => tonic_builder.compile(&protos, &includes), @@ -70,17 +93,32 @@ pub fn compile_from_buf_workspace( pub fn compile_from_buf( tonic_builder: tonic_build::Builder, config: Option, +) -> Result<(), TonicBufBuildError> { + compile_from_buf_with_config::<&'static str>(tonic_builder, config, TonicBufConfig::default()) +} + +pub fn compile_from_buf_with_config>( + tonic_builder: tonic_build::Builder, + config: Option, + tonic_buf_config: TonicBufConfig

, ) -> Result<(), TonicBufBuildError> { let export_dir = tempdir(); defer! { // This is just cleanup, it's not important if it fails let _ = std::fs::remove_dir(&export_dir); } - let buf = buf::BufYaml::load("buf.yaml")?; + let buf_dir = tonic_buf_config + .buf_dir + .as_ref() + .map(|p| p.as_ref()) + .unwrap_or(".".as_ref()); + let mut buf_file = PathBuf::from(buf_dir); + buf_file.push("buf.yaml"); + let buf = buf::BufYaml::load(buf_file.as_path())?; buf::export_all(&buf, &export_dir)?; - let protos = buf::ls_files()?; - let includes = [".", export_dir.to_str().unwrap()]; + let protos = buf::ls_files(buf_dir)?; + let includes = [buf_dir, &export_dir]; match config { None => tonic_builder.compile(&protos, &includes),