diff --git a/rust/cargo/build.rs b/rust/cargo/build.rs index c69ef7c393b8..5351880e987c 100644 --- a/rust/cargo/build.rs +++ b/rust/cargo/build.rs @@ -5,6 +5,8 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +const VERSION: &str = env!("CARGO_PKG_VERSION"); + fn main() { cc::Build::new() .flag("-std=c99") @@ -12,10 +14,12 @@ fn main() { // .flag("-flto=thin") .warnings(false) .include("libupb") + .include("libupb/third_party/utf8_range") .file("libupb/upb/upb.c") .file("libupb/third_party/utf8_range/utf8_range.c") .define("UPB_BUILD_API", Some("1")) .compile("libupb"); let path = std::path::Path::new("libupb"); - println!("cargo:include={}", path.canonicalize().unwrap().display()) + println!("cargo:include={}", path.canonicalize().unwrap().display()); + println!("cargo:version={}", VERSION); } diff --git a/rust/protobuf_codegen/Cargo.toml b/rust/protobuf_codegen/Cargo.toml new file mode 100644 index 000000000000..bcec2cdc30f6 --- /dev/null +++ b/rust/protobuf_codegen/Cargo.toml @@ -0,0 +1,9 @@ +[package] +edition = "2021" +name = "protobuf-codegen" +readme = "README.md" +version = "4.27.3-beta.0" + +[dependencies] +walkdir = "2.5.0" +cc = "1.1.6" \ No newline at end of file diff --git a/rust/protobuf_codegen/src/lib.rs b/rust/protobuf_codegen/src/lib.rs new file mode 100644 index 000000000000..534e039011c0 --- /dev/null +++ b/rust/protobuf_codegen/src/lib.rs @@ -0,0 +1,151 @@ +use std::fs::{self, OpenOptions}; +use std::io::{self, prelude::*}; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +#[derive(Debug)] +pub struct CodeGen { + inputs: Vec, + output_dir: PathBuf, + protoc_path: PathBuf, + protoc_gen_upb_path: PathBuf, + protoc_gen_upb_minitable_path: PathBuf, + includes: Vec, +} + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +impl CodeGen { + pub fn new() -> Self { + Self { + inputs: Vec::new(), + output_dir: std::env::current_dir().unwrap().join("src").join("protos"), + protoc_path: PathBuf::from("protoc"), + protoc_gen_upb_path: PathBuf::from("protoc-gen-upb"), + protoc_gen_upb_minitable_path: PathBuf::from("protoc-gen-upb_minitable"), + includes: Vec::new(), + } + } + + pub fn input(&mut self, input: impl AsRef) -> &mut Self { + self.inputs.push(input.as_ref().to_owned()); + self + } + + pub fn inputs(&mut self, inputs: impl IntoIterator>) -> &mut Self { + self.inputs.extend(inputs.into_iter().map(|input| input.as_ref().to_owned())); + self + } + + pub fn output_dir(&mut self, output_dir: impl AsRef) -> &mut Self { + self.output_dir = output_dir.as_ref().to_owned(); + self + } + + pub fn protoc_path(&mut self, protoc_path: impl AsRef) -> &mut Self { + self.protoc_path = protoc_path.as_ref().to_owned(); + self + } + + pub fn protoc_gen_upb_path(&mut self, protoc_gen_upb_path: impl AsRef) -> &mut Self { + self.protoc_gen_upb_path = protoc_gen_upb_path.as_ref().to_owned(); + self + } + + pub fn protoc_gen_upb_minitable_path( + &mut self, + protoc_gen_upb_minitable_path: impl AsRef, + ) -> &mut Self { + self.protoc_gen_upb_minitable_path = protoc_gen_upb_minitable_path.as_ref().to_owned(); + self + } + + pub fn include(&mut self, include: impl AsRef) -> &mut Self { + self.includes.push(include.as_ref().to_owned()); + self + } + + pub fn includes(&mut self, includes: impl Iterator>) -> &mut Self { + self.includes.extend(includes.into_iter().map(|include| include.as_ref().to_owned())); + self + } + + /// Generate Rust, upb, and upb minitables. Build and link the C code. + pub fn compile(&self) -> Result<(), String> { + let libupb_version = std::env::var("DEP_LIBUPB_VERSION").expect("DEP_LIBUPB_VERSION should have been set, make sure that the Protobuf crate is a dependency"); + if VERSION != libupb_version { + panic!( + "protobuf-codegen version {} does not match protobuf version {}.", + VERSION, libupb_version + ); + } + + let mut cmd = std::process::Command::new(&self.protoc_path); + for input in &self.inputs { + cmd.arg(input); + } + cmd.arg(format!("--rust_out={}", self.output_dir.display())) + .arg("--rust_opt=experimental-codegen=enabled,kernel=upb") + .arg(format!("--plugin=protoc-gen-upb={}", self.protoc_gen_upb_path.display())) + .arg(format!( + "--plugin=protoc-gen-upb_minitable={}", + self.protoc_gen_upb_minitable_path.display() + )) + .arg(format!("--upb_minitable_out={}", self.output_dir.display())) + .arg(format!("--upb_out={}", self.output_dir.display())); + for include in &self.includes { + cmd.arg(format!("--proto_path={}", include.display())); + } + let output = cmd.output().map_err(|e| format!("failed to run protoc: {}", e))?; + println!("{}", std::str::from_utf8(&output.stdout).unwrap()); + eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap()); + assert!(output.status.success()); + + let mut cc_build = cc::Build::new(); + cc_build + .include( + std::env::var_os("DEP_LIBUPB_INCLUDE") + .expect("DEP_LIBUPB_INCLUDE should have been set, make sure that the Protobuf crate is a dependency"), + ) + .include(self.output_dir.clone()) + .flag("-std=c99"); + for entry in WalkDir::new(&self.output_dir) { + if let Ok(entry) = entry { + let path = entry.path(); + let file_name = path.file_name().unwrap().to_str().unwrap(); + if file_name.ends_with(".upb_minitable.c") { + cc_build.file(path); + } + if file_name.ends_with(".upb.h") { + Self::strip_upb_inline(&path); + cc_build.file(path.with_extension("c")); + } + if file_name.ends_with(".pb.rs") { + Self::fix_rs_gencode(&path); + } + } + } + cc_build.compile(&format!("{}_upb_gen_code", env!("CARGO_CRATE_NAME"))); + Ok(()) + } + + // Remove UPB_INLINE from the .upb.h file. + fn strip_upb_inline(header: &Path) { + let contents = fs::read_to_string(header).unwrap().replace("UPB_INLINE ", ""); + let mut file = + OpenOptions::new().write(true).truncate(true).open(header.with_extension("c")).unwrap(); + file.write(contents.as_bytes()).unwrap(); + } + + // Adjust the generated Rust code to work with the crate structure. + fn fix_rs_gencode(path: &Path) { + let contents = fs::read_to_string(path) + .unwrap() + .replace("crate::", "") + .replace("protobuf_upb", "protobuf") + .replace("::__pb", "__pb") + .replace("::__std", "__std"); + let mut file = OpenOptions::new().write(true).truncate(true).open(path).unwrap(); + file.write(contents.as_bytes()).unwrap(); + } +}