Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ jobs:

semver:
runs-on: ubuntu-latest
env:
SKIP_GRPC_RUST_PROTO_CODEGEN: 1
steps:
- uses: actions/checkout@v6
- uses: obi1kenobi/cargo-semver-checks-action@v2
Expand Down
126 changes: 99 additions & 27 deletions examples/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
*/

use std::env;
use std::path::PathBuf;
use std::fs;
use std::path::{Path, PathBuf};

#[allow(clippy::too_many_lines)]
fn main() {
println!("cargo:rerun-if-changed=build.rs");

Expand Down Expand Up @@ -79,44 +81,89 @@ fn main() {
.unwrap();

println!("cargo:rerun-if-env-changed=GRPC_RUST_REGENERATE_PROTO");
println!("cargo:rerun-if-env-changed=SKIP_GRPC_RUST_PROTO_CODEGEN");
let grpc_helloworld = env::var_os("CARGO_FEATURE_GRPC_HELLOWORLD").is_some();
let grpc_routeguide = env::var_os("CARGO_FEATURE_GRPC_ROUTEGUIDE").is_some();

if (grpc_helloworld || grpc_routeguide) && env::var_os("GRPC_RUST_REGENERATE_PROTO").is_some() {
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());

let generated_dir = manifest_dir.join("generated");
if generated_dir.exists() {
std::fs::remove_dir_all(&generated_dir)
.expect("All files in generated/ directory should be deletable");
if grpc_helloworld || grpc_routeguide {
let hw_proto = Path::new("proto/helloworld/helloworld.proto");
let rg_proto = Path::new("proto/routeguide/route_guide.proto");
let hw_gen = Path::new("generated/helloworld/generated.rs");
let rg_gen = Path::new("generated/routeguide/generated.rs");

println!("cargo:rerun-if-changed={}", hw_proto.display());
println!("cargo:rerun-if-changed={}", rg_proto.display());
println!("cargo:rerun-if-changed={}", hw_gen.display());
println!("cargo:rerun-if-changed={}", rg_gen.display());

let skip_codegen = env::var_os("SKIP_GRPC_RUST_PROTO_CODEGEN").is_some();
let force_regenerate = env::var_os("GRPC_RUST_REGENERATE_PROTO").is_some();

if skip_codegen {
if force_regenerate {
println!(
"cargo:warning=Both SKIP_GRPC_RUST_PROTO_CODEGEN and GRPC_RUST_REGENERATE_PROTO are set. Skipping code generation."
);
}
assert!(
hw_gen.exists() && rg_gen.exists(),
"SKIP_GRPC_RUST_PROTO_CODEGEN is set, but generated files are missing in generated/"
);
} else {
let generated_missing = !hw_gen.exists() || !rg_gen.exists();
let proto_newer = match (
fs::metadata(hw_proto).and_then(|m| m.modified()),
fs::metadata(rg_proto).and_then(|m| m.modified()),
fs::metadata(hw_gen).and_then(|m| m.modified()),
fs::metadata(rg_gen).and_then(|m| m.modified()),
) {
(Ok(hw_p), Ok(rg_p), Ok(hw_g), Ok(rg_g)) => hw_p > hw_g || rg_p > rg_g,
_ => true,
};

if force_regenerate || generated_missing || proto_newer {
if has_protoc() {
let generated_dir = Path::new("generated");
if generated_dir.exists() {
let _ = fs::remove_dir_all(generated_dir);
}

grpc_protobuf_build::CodeGen::new()
.output_dir(generated_dir.join("helloworld"))
.input("helloworld.proto")
.include("proto/helloworld")
.client_only()
.compile()
.unwrap();

grpc_protobuf_build::CodeGen::new()
.output_dir(generated_dir.join("routeguide"))
.input("route_guide.proto")
.include("proto/routeguide")
.client_only()
.compile()
.unwrap();
} else if generated_missing {
panic!(
"Cannot generate protobuf code: protoc is not available and generated files are missing in generated/"
);
} else {
println!(
"cargo:warning=protoc not found; skipping proto regeneration and using checked-in files."
);
}
}
}

grpc_protobuf_build::CodeGen::new()
.output_dir(generated_dir.join("helloworld"))
.input("helloworld.proto")
.include(manifest_dir.join("proto/helloworld"))
.client_only()
.compile()
.unwrap();

grpc_protobuf_build::CodeGen::new()
.output_dir(generated_dir.join("routeguide"))
.input("route_guide.proto")
.include(manifest_dir.join("proto/routeguide"))
.client_only()
.compile()
.unwrap();
}

if env::var_os("CARGO_FEATURE_GRPC_GCP").is_some() {
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let dependencies = protobuf_well_known_types::get_dependency("protobuf_well_known_types")
.into_iter()
.map(|d| d.into())
.map(std::convert::Into::into)
.collect();

grpc_protobuf_build::CodeGen::new()
.include(manifest_dir.join("proto/googleapis"))
.include("proto/googleapis")
.inputs([
"google/pubsub/v1/pubsub.proto",
"google/pubsub/v1/schema.proto",
Expand All @@ -134,6 +181,31 @@ fn main() {
}
}

fn has_protoc() -> bool {
#[cfg(feature = "protoc-gen-rust-grpc")]
if protoc_gen_rust_grpc::protoc().is_file() {
return true;
}
if env::var_os("GRPC_RUST_PROTOC_DIR").is_some_and(|dir| {
!dir.is_empty()
&& (Path::new(&dir).join("protoc").is_file()
|| Path::new(&dir).join("protoc.exe").is_file())
}) {
return true;
}
if env::var_os("PROTOC").is_some_and(|p| !p.is_empty() && Path::new(&p).is_file()) {
return true;
}
if let Some(path_var) = env::var_os("PATH") {
for dir in env::split_paths(&path_var) {
if dir.join("protoc").is_file() || dir.join("protoc.exe").is_file() {
return true;
}
}
}
false
}

// Manually define the json.helloworld.Greeter service which used a custom JsonCodec to use json
// serialization instead of protobuf for sending messages on the wire.
// This will result in generated client and server code which relies on its request, response and
Expand Down
1 change: 1 addition & 0 deletions examples/generated/helloworld/helloworld_grpc.pb.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// Generated client implementations.
pub mod greeter_client {
#![allow(unused_imports, dead_code, missing_docs, clippy::wildcard_imports)]
use grpc::client::*;
use grpc_protobuf::*;
use grpc_protobuf::client::*;
Expand Down
1 change: 1 addition & 0 deletions examples/generated/routeguide/route_guide_grpc.pb.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// Generated client implementations.
pub mod route_guide_client {
#![allow(unused_imports, dead_code, missing_docs, clippy::wildcard_imports)]
use grpc::client::*;
use grpc_protobuf::*;
use grpc_protobuf::client::*;
Expand Down
104 changes: 87 additions & 17 deletions grpc-protobuf/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,96 @@
*/

use std::env;
use std::path::PathBuf;
use std::fs;
use std::path::Path;

fn main() {
println!("cargo:rerun-if-changed=build.rs");
let proto_path = Path::new("third_party/googleapis/google/rpc/status.proto");
let generated_dir = Path::new("generated");
let gen_marker = Path::new("generated/google/rpc/generated.rs");

println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", proto_path.display());
println!("cargo:rerun-if-changed={}", gen_marker.display());
println!("cargo:rerun-if-env-changed=GRPC_RUST_REGENERATE_PROTO");
if env::var_os("GRPC_RUST_REGENERATE_PROTO").is_some() {
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let dependencies = protobuf_well_known_types::get_dependency("protobuf_well_known_types")
.into_iter()
.map(|d| d.into())
.collect();

grpc_protobuf_build::CodeGen::new()
.output_dir(manifest_dir.join("generated"))
.include(manifest_dir.join("third_party/googleapis"))
.inputs(["google/rpc/status.proto"])
.dependencies(dependencies)
.client_only()
.compile()
.unwrap();
println!("cargo:rerun-if-env-changed=SKIP_GRPC_RUST_PROTO_CODEGEN");

let skip_codegen = env::var_os("SKIP_GRPC_RUST_PROTO_CODEGEN").is_some();
let force_regenerate = env::var_os("GRPC_RUST_REGENERATE_PROTO").is_some();

if skip_codegen {
if force_regenerate {
println!(
"cargo:warning=Both SKIP_GRPC_RUST_PROTO_CODEGEN and GRPC_RUST_REGENERATE_PROTO are set. Skipping code generation."
);
}
assert!(
gen_marker.exists(),
"SKIP_GRPC_RUST_PROTO_CODEGEN is set, but generated files are missing at {}",
gen_marker.display()
);
return;
}

let generated_missing = !generated_dir.exists() || !gen_marker.exists();
let proto_newer = match (fs::metadata(proto_path), fs::metadata(gen_marker)) {
(Ok(p_meta), Ok(g_meta)) => match (p_meta.modified(), g_meta.modified()) {
(Ok(p_time), Ok(g_time)) => p_time > g_time,
_ => true,
},
_ => true,
};

if force_regenerate || generated_missing || proto_newer {
if has_protoc() {
if generated_dir.exists() {
let _ = fs::remove_dir_all(generated_dir);
}

let dependencies =
protobuf_well_known_types::get_dependency("protobuf_well_known_types")
.into_iter()
.map(std::convert::Into::into)
.collect();

grpc_protobuf_build::CodeGen::new()
.output_dir(generated_dir)
.include("third_party/googleapis")
.inputs(["google/rpc/status.proto"])
.dependencies(dependencies)
.client_only()
.compile()
.unwrap();
} else if generated_missing {
panic!(
"Cannot generate protobuf code: protoc is not available and generated files are missing at {}",
generated_dir.display()
);
} else {
println!(
"cargo:warning=protoc not found; skipping proto regeneration and using checked-in files."
);
}
}
}

fn has_protoc() -> bool {
if env::var_os("GRPC_RUST_PROTOC_DIR").is_some_and(|dir| {
!dir.is_empty()
&& (Path::new(&dir).join("protoc").is_file()
|| Path::new(&dir).join("protoc.exe").is_file())
}) {
return true;
}
if env::var_os("PROTOC").is_some_and(|p| !p.is_empty() && Path::new(&p).is_file()) {
return true;
}
if let Some(path_var) = env::var_os("PATH") {
for dir in env::split_paths(&path_var) {
if dir.join("protoc").is_file() || dir.join("protoc.exe").is_file() {
return true;
}
}
}
false
}
Loading