Skip to content

Commit

Permalink
Create an initial version of protobuf-codegen. This exposes an initia…
Browse files Browse the repository at this point in the history
…l working version of codegen for use with cargo.

PiperOrigin-RevId: 658062486
  • Loading branch information
dbenson24 authored and copybara-github committed Jul 31, 2024
1 parent 7c93804 commit 9d628f8
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 1 deletion.
6 changes: 5 additions & 1 deletion rust/cargo/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
// 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")
// TODO: Come up with a way to enable lto
// .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);
}
9 changes: 9 additions & 0 deletions rust/protobuf_codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
151 changes: 151 additions & 0 deletions rust/protobuf_codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf>,
output_dir: PathBuf,
protoc_path: PathBuf,
protoc_gen_upb_path: PathBuf,
protoc_gen_upb_minitable_path: PathBuf,
includes: Vec<PathBuf>,
}

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<Path>) -> &mut Self {
self.inputs.push(input.as_ref().to_owned());
self
}

pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &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<Path>) -> &mut Self {
self.output_dir = output_dir.as_ref().to_owned();
self
}

pub fn protoc_path(&mut self, protoc_path: impl AsRef<Path>) -> &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<Path>) -> &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<Path>,
) -> &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<Path>) -> &mut Self {
self.includes.push(include.as_ref().to_owned());
self
}

pub fn includes(&mut self, includes: impl Iterator<Item = impl AsRef<Path>>) -> &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();
}
}

0 comments on commit 9d628f8

Please sign in to comment.