From c4c1d4c8b4b359fe920eb6d85a64cf196e0bfa8f Mon Sep 17 00:00:00 2001 From: "Chris (ChrisJr404)" <11917633+ChrisJr404@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:33:01 -0400 Subject: [PATCH] feat(prost-build): support host specific PROTOC environment variable --- prost-build/src/config.rs | 47 ++++++++++++++++++++++++++++++++++++--- prost-build/src/lib.rs | 11 +++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/prost-build/src/config.rs b/prost-build/src/config.rs index 055a6f97a..b41146f8d 100644 --- a/prost-build/src/config.rs +++ b/prost-build/src/config.rs @@ -1267,10 +1267,34 @@ pub fn error_message_protoc_not_found() -> String { } /// Returns the path to the `protoc` binary. +/// +/// When Cargo runs a build script it sets the `HOST` environment variable to the host target +/// triple. If a host specific `_PROTOC` variable is set (for example +/// `X86_64_UNKNOWN_LINUX_GNU_PROTOC`), it takes precedence over the plain `PROTOC` variable. +/// This makes it possible to point at a different `protoc` per build host from a single `[env]` +/// table in `.cargo/config.toml`. If neither variable is set, `protoc` is looked up on the `PATH`. pub fn protoc_from_env() -> PathBuf { - env::var_os("PROTOC") - .map(PathBuf::from) - .unwrap_or(PathBuf::from("protoc")) + host_protoc_from_env() + .or_else(|| env::var_os("PROTOC").map(PathBuf::from)) + .unwrap_or_else(|| PathBuf::from("protoc")) +} + +/// Returns the host specific `protoc` path, e.g. from `X86_64_UNKNOWN_LINUX_GNU_PROTOC`. +/// +/// Returns `None` when the `HOST` triple is not available (so outside of a build script), or when +/// the host specific variable is not set. +fn host_protoc_from_env() -> Option { + let host = env::var("HOST").ok()?; + env::var_os(host_protoc_var(&host)).map(PathBuf::from) +} + +/// Builds the host specific `protoc` environment variable name from a target triple. +/// +/// The triple is uppercased and any `-` or `.` is replaced with `_`, so the name is a valid +/// environment variable. This follows the same convention used by other build time crates such as +/// `openssl-sys`. +fn host_protoc_var(host: &str) -> String { + format!("{}_PROTOC", host.replace(['-', '.'], "_").to_uppercase()) } /// Returns the path to the Protobuf include directory. @@ -1312,6 +1336,23 @@ mod tests { }; } + #[test] + fn test_host_protoc_var() { + assert_eq!( + host_protoc_var("x86_64-unknown-linux-gnu"), + "X86_64_UNKNOWN_LINUX_GNU_PROTOC" + ); + assert_eq!( + host_protoc_var("aarch64-pc-windows-msvc"), + "AARCH64_PC_WINDOWS_MSVC_PROTOC" + ); + // Triples containing a `.` are normalized to a valid environment variable name. + assert_eq!( + host_protoc_var("thumbv8m.main-none-eabi"), + "THUMBV8M_MAIN_NONE_EABI_PROTOC" + ); + } + #[test] fn test_error_protoc_not_found() { let mut config = Config::new(); diff --git a/prost-build/src/lib.rs b/prost-build/src/lib.rs index 8da103038..d7da90c62 100644 --- a/prost-build/src/lib.rs +++ b/prost-build/src/lib.rs @@ -114,6 +114,17 @@ //! PROTOC=/usr/local/bin/protoc //! ``` //! +//! When building on more than one host, a host specific `_PROTOC` variable takes precedence +//! over `PROTOC`. The prefix is the host target triple, uppercased with `-` and `.` replaced by +//! `_`, so on an `x86_64-unknown-linux-gnu` host the variable is `X86_64_UNKNOWN_LINUX_GNU_PROTOC`. +//! This lets a single `[env]` table in `.cargo/config.toml` point at a different `protoc` per host: +//! +//! ```toml +//! [env] +//! X86_64_UNKNOWN_LINUX_GNU_PROTOC = { value = "tools/x86_64-linux/protoc", relative = true } +//! AARCH64_APPLE_DARWIN_PROTOC = { value = "tools/aarch64-macos/protoc", relative = true } +//! ``` +//! //! Alternatively, the path to `protoc` executable can be explicitly set //! via [`Config::protoc_executable()`]. //!