diff --git a/Cargo.toml b/Cargo.toml index e8a8d920..689a1be1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,47 @@ -[package] -name = "ttrpc" -version = "0.8.4" -authors = ["The AntFin Kata Team "] +[workspace] +members = [ + "./compiler", + "./ttrpc-codegen", + "./example", + "./" +] +resolver = "2" + +[workspace.package] edition = "2018" license = "Apache-2.0" -keywords = ["ttrpc", "protobuf", "rpc"] readme = "README.md" repository = "https://github.com/containerd/ttrpc-rust" homepage = "https://github.com/containerd/ttrpc-rust" +authors = ["The AntFin Kata Team "] + +[workspace.dependencies] +# The dependencies in this section can be used by the crates in this +# workspace by specifying `workspace = true` instead of the crate +# version. For example, for protobuf: +# protobuf = { workspace = true } +ttrpc = { version = "0.8.4", path = "./" } +ttrpc-codegen = { version = "0.5.0", path = "./ttrpc-codegen" } +ttrpc-compiler = { version = "0.7.0", path = "./compiler" } +protobuf = "3.7.2" +protobuf-codegen = "3.7.2" +protobuf-support = "3.7.2" + +[package] +name = "ttrpc" +version = "0.8.4" +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +keywords = ["ttrpc", "protobuf", "rpc"] +readme = "README.md" description = "A Rust version of ttrpc." rust-version = "1.70" [dependencies] -protobuf = { version = "3.1.0" } +protobuf = { workspace = true } libc = { version = "0.2.59", features = [ "extra_traits" ] } nix = "0.26.2" log = "0.4" @@ -20,7 +49,7 @@ byteorder = "1.3.2" thiserror = "1.0" async-trait = { version = "0.1.31", optional = true } async-stream = { version = "0.3.6", optional = true } -tokio = { version = "1", features = ["rt", "sync", "io-util", "macros", "time"], optional = true } +tokio = { version = "1", features = ["rt", "sync", "io-util", "macros", "time", "net"], optional = true } futures = { version = "0.3", optional = true } crossbeam = "0.8.0" @@ -33,7 +62,7 @@ tokio-vsock = { version = "0.7.0", optional = true } [build-dependencies] # lock home to avoid conflict with latest version home = "=0.5.9" -protobuf-codegen = "3.1.0" +protobuf-codegen = { workspace = true } [features] default = ["sync"] diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..092fbf2e --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,27 @@ +# Release Process + +This document describes the steps to release a new version of the crate or wasi-demo-app images. + +## Crate Release Process + +### Release Steps + +1. Bump package and dependency versions in: + * `./compiler/Cargo.toml`: Bump the package version as needed. + * `./ttrpc-codegen/Cargo.toml`: Bump the package version as needed. + * `./Cargo.toml`: Bump package version as needed. Then bump the workspace dependencies version to match the respective crates versions. +2. Commit the changes and get them merged in the repo. +2. Dry run the `cargo publish` command as follow + ```bash + cargo +nightly publish \ + -Z package-workspace \ + --dry-run \ + --locked \ + -p ttrpc \ + -p ttrpc-codegen \ + -p ttrpc-compiler + ``` +2. If the dry run succeeds, publish the crates that need publishing using `cargo publihs -p ` in the following order + 1. `ttrpc-compiler` + 2. `ttrpc-codegen` + 3. `ttrpc` \ No newline at end of file diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 3bd6fb3b..67b83be4 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -1,21 +1,21 @@ [package] name = "ttrpc-compiler" version = "0.7.0" -edition = "2018" -authors = ["The AntFin Kata Team "] -license = "Apache-2.0" +edition = { workspace = true } +license = { workspace = true } +authors = { workspace = true } +readme = "README.md" keywords = ["compiler", "ttrpc", "protobuf"] description = "ttRPC compiler for ttrpc" categories = ["network-programming"] repository = "https://github.com/containerd/ttrpc-rust/tree/master/compiler" homepage = "https://github.com/containerd/ttrpc-rust/tree/master/compiler" -readme = "README.md" [dependencies] # lock home to avoid conflict with latest version home = "=0.5.9" -protobuf = "2.27.1" -protobuf-codegen = "2.27.1" +protobuf = { workspace = true } +protobuf-codegen = { workspace = true } prost = "0.8" prost-build = "0.8" prost-types = "0.8" diff --git a/compiler/src/codegen.rs b/compiler/src/codegen.rs index 6b7757d4..e98303ff 100644 --- a/compiler/src/codegen.rs +++ b/compiler/src/codegen.rs @@ -42,18 +42,18 @@ use std::{ io::BufRead, }; -use crate::Customize; +use crate::{ + util::proto_path_to_rust_mod, util::scope::RootScope, util::writer::CodeWriter, Customize, +}; use protobuf::{ - compiler_plugin::{GenRequest, GenResult}, descriptor::*, - descriptorx::*, plugin::{ - CodeGeneratorRequest, CodeGeneratorResponse, CodeGeneratorResponse_Feature, - CodeGeneratorResponse_File, + code_generator_response::Feature as CodeGeneratorResponse_Feature, + code_generator_response::File as CodeGeneratorResponse_File, CodeGeneratorRequest, + CodeGeneratorResponse, }, Message, }; -use protobuf_codegen::code_writer::CodeWriter; use std::fs::File; use std::io::{self, stdin, stdout, Write}; use std::path::Path; @@ -91,7 +91,7 @@ impl<'a> MethodGen<'a> { format!( "super::{}", self.root_scope - .find_message(self.proto.get_input_type()) + .find_message(self.proto.input_type()) .rust_fq_name() ) } @@ -100,16 +100,13 @@ impl<'a> MethodGen<'a> { format!( "super::{}", self.root_scope - .find_message(self.proto.get_output_type()) + .find_message(self.proto.output_type()) .rust_fq_name() ) } fn method_type(&self) -> (MethodType, String) { - match ( - self.proto.get_client_streaming(), - self.proto.get_server_streaming(), - ) { + match (self.proto.client_streaming(), self.proto.server_streaming()) { (false, false) => (MethodType::Unary, fq_grpc("MethodType::Unary")), (true, false) => ( MethodType::ClientStreaming, @@ -128,11 +125,11 @@ impl<'a> MethodGen<'a> { } fn name(&self) -> String { - to_snake_case(self.proto.get_name()) + to_snake_case(self.proto.name()) } fn struct_name(&self) -> String { - to_camel_case(self.proto.get_name()) + to_camel_case(self.proto.name()) } fn const_method_name(&self) -> String { @@ -145,7 +142,7 @@ impl<'a> MethodGen<'a> { fn write_handler(&self, w: &mut CodeWriter) { w.block( - &format!("struct {}Method {{", self.struct_name()), + format!("struct {}Method {{", self.struct_name()), "}", |w| { w.write_line(format!( @@ -163,13 +160,13 @@ impl<'a> MethodGen<'a> { } fn write_handler_impl(&self, w: &mut CodeWriter) { - w.block(&format!("impl ::ttrpc::MethodHandler for {}Method {{", self.struct_name()), "}", + w.block(format!("impl ::ttrpc::MethodHandler for {}Method {{", self.struct_name()), "}", |w| { w.block("fn handler(&self, ctx: ::ttrpc::TtrpcContext, req: ::ttrpc::Request) -> ::ttrpc::Result<()> {", "}", |w| { w.write_line(format!("::ttrpc::request_handler!(self, ctx, req, {}, {}, {});", - proto_path_to_rust_mod(self.root_scope.find_message(self.proto.get_input_type()).get_scope().get_file_descriptor().get_name()), - self.root_scope.find_message(self.proto.get_input_type()).rust_name(), + proto_path_to_rust_mod(self.root_scope.find_message(self.proto.input_type()).fd.name()), + self.root_scope.find_message(self.proto.input_type()).rust_name(), self.name())); w.write_line("Ok(())"); }); @@ -180,20 +177,20 @@ impl<'a> MethodGen<'a> { w.write_line("#[async_trait]"); match self.method_type().0 { MethodType::Unary => { - w.block(&format!("impl ::ttrpc::r#async::MethodHandler for {}Method {{", self.struct_name()), "}", + w.block(format!("impl ::ttrpc::r#async::MethodHandler for {}Method {{", self.struct_name()), "}", |w| { w.block("async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, req: ::ttrpc::Request) -> ::ttrpc::Result<::ttrpc::Response> {", "}", |w| { w.write_line(format!("::ttrpc::async_request_handler!(self, ctx, req, {}, {}, {});", - proto_path_to_rust_mod(self.root_scope.find_message(self.proto.get_input_type()).get_scope().get_file_descriptor().get_name()), - self.root_scope.find_message(self.proto.get_input_type()).rust_name(), + proto_path_to_rust_mod(self.root_scope.find_message(self.proto.input_type()).fd.name()), + self.root_scope.find_message(self.proto.input_type()).rust_name(), self.name())); }); }); } // only receive MethodType::ClientStreaming => { - w.block(&format!("impl ::ttrpc::r#async::StreamHandler for {}Method {{", self.struct_name()), "}", + w.block(format!("impl ::ttrpc::r#async::StreamHandler for {}Method {{", self.struct_name()), "}", |w| { w.block("async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, inner: ::ttrpc::r#async::StreamInner) -> ::ttrpc::Result> {", "}", |w| { @@ -204,20 +201,20 @@ impl<'a> MethodGen<'a> { } // only send MethodType::ServerStreaming => { - w.block(&format!("impl ::ttrpc::r#async::StreamHandler for {}Method {{", self.struct_name()), "}", + w.block(format!("impl ::ttrpc::r#async::StreamHandler for {}Method {{", self.struct_name()), "}", |w| { w.block("async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, mut inner: ::ttrpc::r#async::StreamInner) -> ::ttrpc::Result> {", "}", |w| { w.write_line(format!("::ttrpc::async_server_streamimg_handler!(self, ctx, inner, {}, {}, {});", - proto_path_to_rust_mod(self.root_scope.find_message(self.proto.get_input_type()).get_scope().get_file_descriptor().get_name()), - self.root_scope.find_message(self.proto.get_input_type()).rust_name(), + proto_path_to_rust_mod(self.root_scope.find_message(self.proto.input_type()).fd.name()), + self.root_scope.find_message(self.proto.input_type()).rust_name(), self.name())); }); }); } // receive and send MethodType::Duplex => { - w.block(&format!("impl ::ttrpc::r#async::StreamHandler for {}Method {{", self.struct_name()), "}", + w.block(format!("impl ::ttrpc::r#async::StreamHandler for {}Method {{", self.struct_name()), "}", |w| { w.block("async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, inner: ::ttrpc::r#async::StreamInner) -> ::ttrpc::Result> {", "}", |w| { @@ -276,13 +273,13 @@ impl<'a> MethodGen<'a> { fn write_client(&self, w: &mut CodeWriter) { let method_name = self.name(); if let MethodType::Unary = self.method_type().0 { - w.pub_fn(&self.unary(&method_name), |w| { + w.pub_fn(self.unary(&method_name), |w| { w.write_line(format!("let mut cres = {}::new();", self.output())); w.write_line(format!( "::ttrpc::client_request!(self, ctx, req, \"{}.{}\", \"{}\", cres);", self.package_name, self.service_name, - &self.proto.get_name(), + &self.proto.name(), )); w.write_line("Ok(cres)"); }); @@ -300,7 +297,7 @@ impl<'a> MethodGen<'a> { "::ttrpc::async_client_request!(self, ctx, req, \"{}.{}\", \"{}\", cres);", self.package_name, self.service_name, - &self.proto.get_name(), + &self.proto.name(), )); }); } @@ -311,7 +308,7 @@ impl<'a> MethodGen<'a> { "::ttrpc::async_client_stream_send!(self, ctx, \"{}.{}\", \"{}\");", self.package_name, self.service_name, - &self.proto.get_name(), + &self.proto.name(), )); }); } @@ -322,7 +319,7 @@ impl<'a> MethodGen<'a> { "::ttrpc::async_client_stream_receive!(self, ctx, req, \"{}.{}\", \"{}\");", self.package_name, self.service_name, - &self.proto.get_name(), + &self.proto.name(), )); }); } @@ -333,7 +330,7 @@ impl<'a> MethodGen<'a> { "::ttrpc::async_client_stream!(self, ctx, \"{}.{}\", \"{}\");", self.package_name, self.service_name, - &self.proto.get_name(), + &self.proto.name(), )); }); } @@ -383,7 +380,7 @@ impl<'a> MethodGen<'a> { let cb = |w: &mut CodeWriter| { w.write_line(format!("Err(::ttrpc::Error::RpcStatus(::ttrpc::get_status(::ttrpc::Code::NOT_FOUND, \"/{}.{}/{} is not supported\".to_string())))", self.package_name, - self.service_name, self.proto.get_name(),)); + self.service_name, self.proto.name(),)); }; if async_on(self.customize, "server") { @@ -403,7 +400,7 @@ impl<'a> MethodGen<'a> { Box::new({}Method{{service: service.clone()}}) as Box);", self.package_name, self.service_name, - self.proto.get_name(), + self.proto.name(), self.struct_name(), method_handler_name, ); @@ -415,7 +412,7 @@ impl<'a> MethodGen<'a> { format!( "methods.insert(\"{}\".to_string(), Box::new({}Method{{service: service.clone()}}) as {});", - self.proto.get_name(), + self.proto.name(), self.struct_name(), "Box" ) @@ -423,7 +420,7 @@ impl<'a> MethodGen<'a> { format!( "streams.insert(\"{}\".to_string(), Arc::new({}Method{{service: service.clone()}}) as {});", - self.proto.get_name(), + self.proto.name(), self.struct_name(), "Arc" ) @@ -443,17 +440,17 @@ impl<'a> ServiceGen<'a> { fn new( proto: &'a ServiceDescriptorProto, file: &FileDescriptorProto, - root_scope: &'a RootScope, + root_scope: &'a RootScope<'a>, customize: &'a Customize, ) -> ServiceGen<'a> { let methods = proto - .get_method() + .method .iter() .map(|m| { MethodGen::new( m, - file.get_package().to_string(), - util::to_camel_case(proto.get_name()), + file.package().to_string(), + util::to_camel_case(proto.name()), root_scope, customize, ) @@ -464,12 +461,12 @@ impl<'a> ServiceGen<'a> { proto, methods, customize, - package_name: file.get_package().to_string(), + package_name: file.package().to_string(), } } fn service_name(&self) -> String { - util::to_camel_case(self.proto.get_name()) + util::to_camel_case(self.proto.name()) } fn service_path(&self) -> String { @@ -510,7 +507,7 @@ impl<'a> ServiceGen<'a> { w.impl_self_block(self.client_name(), |w| { w.pub_fn("new(client: ::ttrpc::Client) -> Self", |w| { - w.expr_block(&self.client_name(), |w| { + w.expr_block(self.client_name(), |w| { w.write_line("client,"); }); }); @@ -532,7 +529,7 @@ impl<'a> ServiceGen<'a> { w.impl_self_block(self.client_name(), |w| { w.pub_fn("new(client: ::ttrpc::r#async::Client) -> Self", |w| { - w.expr_block(&self.client_name(), |w| { + w.expr_block(self.client_name(), |w| { w.write_line("client,"); }); }); @@ -676,60 +673,60 @@ fn write_generated_common(w: &mut CodeWriter) { fn gen_file( file: &FileDescriptorProto, - root_scope: &RootScope, + root_scope: &RootScope<'_>, customize: &Customize, -) -> Option { - if file.get_service().is_empty() { +) -> Option { + if file.service.is_empty() { return None; } - let base = protobuf::descriptorx::proto_path_to_rust_mod(file.get_name()); + let base = proto_path_to_rust_mod(file.name()); - let mut v = Vec::new(); - { - let mut w = CodeWriter::new(&mut v); + let mut w = CodeWriter::new(); - write_generated_by(&mut w, "ttrpc-compiler", env!("CARGO_PKG_VERSION")); + write_generated_by(&mut w, "ttrpc-compiler", env!("CARGO_PKG_VERSION")); - w.write_line("use protobuf::{CodedInputStream, CodedOutputStream, Message};"); - w.write_line("use std::collections::HashMap;"); - w.write_line("use std::sync::Arc;"); - if customize.async_all || customize.async_client || customize.async_server { - w.write_line("use async_trait::async_trait;"); - } + w.write_line("use protobuf::{CodedInputStream, CodedOutputStream, Message};"); + w.write_line("use std::collections::HashMap;"); + w.write_line("use std::sync::Arc;"); + if customize.async_all || customize.async_client || customize.async_server { + w.write_line("use async_trait::async_trait;"); + } - for service in file.get_service() { - w.write_line(""); - ServiceGen::new(service, file, root_scope, customize).write(&mut w); - } + for service in file.service.iter() { + w.write_line(""); + ServiceGen::new(service, file, root_scope, customize).write(&mut w); } - Some(GenResult { - name: base + "_ttrpc.rs", - content: v, - }) + let mut file = CodeGeneratorResponse_File::new(); + file.set_name(format!("{base}_ttrpc.rs")); + file.set_content(w.take_code()); + Some(file) } pub fn gen( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], customize: &Customize, -) -> Vec { +) -> CodeGeneratorResponse { let files_map: HashMap<&str, &FileDescriptorProto> = - file_descriptors.iter().map(|f| (f.get_name(), f)).collect(); + file_descriptors.iter().map(|f| (f.name(), f)).collect(); let root_scope = RootScope { file_descriptors }; - let mut results = Vec::new(); + let mut results = CodeGeneratorResponse::new(); + results.set_supported_features(CodeGeneratorResponse_Feature::FEATURE_PROTO3_OPTIONAL as _); for file_name in files_to_generate { let file = files_map[&file_name[..]]; - if file.get_service().is_empty() { + if file.service.is_empty() { continue; } - results.extend(gen_file(file, &root_scope, customize).into_iter()); + results + .file + .extend(gen_file(file, &root_scope, customize).into_iter()); } results @@ -758,8 +755,8 @@ pub fn gen_and_write( .write(true) .truncate(true) .open(&file_path)?; - for r in &results { - let prefix_name: Vec<&str> = r.name.split('.').collect(); + for r in &results.file { + let prefix_name: Vec<&str> = r.name().split('.').collect(); set.insert(format!("pub mod {};", prefix_name[0])); } for item in &set { @@ -768,11 +765,11 @@ pub fn gen_and_write( file_write.flush()?; } - for r in &results { + for r in &results.file { let mut file_path = out_dir.to_owned(); - file_path.push(&r.name); + file_path.push(r.name()); let mut file_writer = File::create(&file_path)?; - file_writer.write_all(&r.content)?; + file_writer.write_all(r.content().as_bytes())?; file_writer.flush()?; } @@ -793,37 +790,16 @@ pub fn protoc_gen_grpc_rust_main() { fn plugin_main(gen: F) where - F: Fn(&[FileDescriptorProto], &[String]) -> Vec, + F: Fn(&[FileDescriptorProto], &[String]) -> CodeGeneratorResponse, { - plugin_main_2(|r| gen(r.file_descriptors, r.files_to_generate)) + plugin_main_2(|r| gen(&r.proto_file, &r.file_to_generate)) } fn plugin_main_2(gen: F) where - F: Fn(&GenRequest) -> Vec, + F: Fn(&CodeGeneratorRequest) -> CodeGeneratorResponse, { let req = CodeGeneratorRequest::parse_from_reader(&mut stdin()).unwrap(); - let result = gen(&GenRequest { - file_descriptors: req.get_proto_file(), - files_to_generate: req.get_file_to_generate(), - parameter: req.get_parameter(), - }); - let mut resp = CodeGeneratorResponse::new(); - resp.set_supported_features(CodeGeneratorResponse_Feature::FEATURE_PROTO3_OPTIONAL as u64); - resp.set_file( - result - .iter() - .map(|file| { - let mut r = CodeGeneratorResponse_File::new(); - r.set_name(file.name.to_string()); - r.set_content( - std::str::from_utf8(file.content.as_ref()) - .unwrap() - .to_string(), - ); - r - }) - .collect(), - ); - resp.write_to_writer(&mut stdout()).unwrap(); + let result = gen(&req); + result.write_to_writer(&mut stdout()).unwrap(); } diff --git a/compiler/src/util.rs b/compiler/src/util/mod.rs similarity index 91% rename from compiler/src/util.rs rename to compiler/src/util/mod.rs index 42bc30e0..859d8550 100644 --- a/compiler/src/util.rs +++ b/compiler/src/util/mod.rs @@ -13,10 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use protobuf_codegen::code_writer::CodeWriter; use std::fmt; use std::str; +pub mod scope; +pub mod writer; + +use writer::CodeWriter; + // A struct that divide a name into serveral parts that meets rust's guidelines. struct NameSpliter<'a> { name: &'a [u8], @@ -111,9 +115,9 @@ where F: Fn(&mut CodeWriter), { if public { - w.expr_block(&format!("pub async fn {}", sig), cb); + w.expr_block(format!("pub async fn {}", sig), cb); } else { - w.expr_block(&format!("async fn {}", sig), cb); + w.expr_block(format!("async fn {}", sig), cb); } } @@ -131,6 +135,15 @@ where async_fn_block(w, false, sig, cb); } +// proto_name_to_rs is constructor as "{proto_path_to_rust_mod}.rs" +// see https://github.com/stepancheg/rust-protobuf/blob/v3.7.2/protobuf-codegen/src/gen/paths.rs#L43 +pub fn proto_path_to_rust_mod(path: &str) -> String { + protobuf_codegen::proto_name_to_rs(path) + .strip_suffix(".rs") + .unwrap() + .to_string() +} + pub enum MethodType { Unary, ClientStreaming, diff --git a/compiler/src/util/scope.rs b/compiler/src/util/scope.rs new file mode 100644 index 00000000..53c40f3c --- /dev/null +++ b/compiler/src/util/scope.rs @@ -0,0 +1,152 @@ +//! This module contains functionalities that where previously available in +//! the protobuf / protobuf-codegen crates, but were then removed. +//! The missing functionalities have been reimplemented in this module. + +use protobuf::descriptor::{DescriptorProto, FileDescriptorProto}; + +// vendered from https://github.com/stepancheg/rust-protobuf/blob/v3.7.2/protobuf-codegen/src/gen/rust/keywords.rs +fn is_rust_keyword(ident: &str) -> bool { + #[rustfmt::skip] + static RUST_KEYWORDS: &[&str] = &[ + "_", + "as", + "async", + "await", + "break", + "crate", + "dyn", + "else", + "enum", + "extern", + "false", + "fn", + "for", + "if", + "impl", + "in", + "let", + "loop", + "match", + "mod", + "move", + "mut", + "pub", + "ref", + "return", + "static", + "self", + "Self", + "struct", + "super", + "true", + "trait", + "type", + "unsafe", + "use", + "while", + "continue", + "box", + "const", + "where", + "virtual", + "proc", + "alignof", + "become", + "offsetof", + "priv", + "pure", + "sizeof", + "typeof", + "unsized", + "yield", + "do", + "abstract", + "final", + "override", + "macro", + ]; + RUST_KEYWORDS.contains(&ident) +} + +// reimplementation based on https://github.com/stepancheg/rust-protobuf/blob/v3.7.2/protobuf-codegen/src/gen/scope.rs#L26 +// it only implements the `find_message` method with not extra dependencies +pub struct RootScope<'a> { + pub file_descriptors: &'a [FileDescriptorProto], +} + +// re-implementation of https://github.com/stepancheg/rust-protobuf/blob/v3.7.2/protobuf-codegen/src/gen/scope.rs#L340 +// also based on https://github.com/stepancheg/rust-protobuf/blob/v3.7.2/protobuf-codegen/src/gen/scope.rs#L156 +pub struct ScopedMessage<'a> { + pub fd: &'a FileDescriptorProto, + pub path: Vec<&'a DescriptorProto>, + pub msg: &'a DescriptorProto, +} + +impl ScopedMessage<'_> { + pub fn prefix(&self) -> String { + let mut prefix = String::new(); + for m in &self.path { + prefix.push_str(m.name()); + prefix.push('.'); + } + prefix + } + + // rust type name prefix for this scope + pub fn rust_prefix(&self) -> String { + self.prefix().replace(".", "_") + } + + // rust type name of this descriptor + pub fn rust_name(&self) -> String { + let mut r = self.rust_prefix(); + // Only escape if prefix is not empty + if r.is_empty() && is_rust_keyword(self.msg.name()) { + r.push_str("message_"); + } + r.push_str(self.msg.name()); + r + } + + // fully-qualified name of this type + pub fn rust_fq_name(&self) -> String { + format!( + "{}::{}", + super::proto_path_to_rust_mod(self.fd.name()), + self.rust_name() + ) + } +} + +impl<'a> RootScope<'a> { + pub fn find_message(&'a self, fqn: impl AsRef) -> ScopedMessage<'a> { + let Some(fqn1) = fqn.as_ref().strip_prefix(".") else { + panic!("name must start with dot: {}", fqn.as_ref()) + }; + for fd in self.file_descriptors { + let mut fqn2 = match fqn1.strip_prefix(fd.package()) { + Some(rest) if fd.package().is_empty() => rest, + Some(rest) if rest.starts_with(".") => &rest[1..], + _ => continue, + }; + + assert!(!fqn2.starts_with(".")); + + let mut pending = Some(fd.message_type.as_slice()); + let mut path = vec![]; + while let Some(msgs) = pending.take() { + for msg in msgs { + fqn2 = match fqn2.strip_prefix(msg.name()) { + Some("") => return ScopedMessage { msg, path, fd }, + Some(rest) if rest.starts_with(".") => &rest[1..], + _ => continue, + }; + path.push(msg); + pending = Some(&msg.nested_type); + break; + } + } + } + panic!("enum not found by name: {}", fqn.as_ref()) + } +} diff --git a/compiler/src/util/writer.rs b/compiler/src/util/writer.rs new file mode 100644 index 00000000..86740442 --- /dev/null +++ b/compiler/src/util/writer.rs @@ -0,0 +1,79 @@ +//! This module contains functionalities that where previously available in +//! the protobuf / protobuf-codegen crates, but were then removed. +//! The missing functionalities have been reimplemented in this module. + +// adapted from https://github.com/stepancheg/rust-protobuf/blob/v3.7.2/protobuf-codegen/src/gen/code_writer.rs#L12 +#[derive(Default)] +pub struct CodeWriter { + writer: String, + indent: String, +} + +impl CodeWriter { + pub fn new() -> CodeWriter { + Self::default() + } + + pub fn code(&self) -> &str { + &self.writer + } + + pub fn take_code(&mut self) -> String { + std::mem::take(&mut self.writer) + } + + pub fn write_line(&mut self, line: impl AsRef) { + if line.as_ref().is_empty() { + self.writer.push('\n'); + } else { + self.writer.push_str(&self.indent); + self.writer.push_str(line.as_ref()); + self.writer.push('\n'); + } + } + + pub fn block( + &mut self, + first_line: impl AsRef, + last_line: impl AsRef, + cb: impl FnOnce(&mut CodeWriter), + ) { + self.write_line(first_line); + self.indented(cb); + self.write_line(last_line); + } + + pub fn expr_block(&mut self, prefix: impl AsRef, cb: impl FnOnce(&mut CodeWriter)) { + self.block(format!("{} {{", prefix.as_ref()), "}", cb); + } + + pub fn indented(&mut self, cb: impl FnOnce(&mut CodeWriter)) { + self.indent.push_str(" "); + cb(self); + self.indent.truncate(self.indent.len() - 4); + } + + pub fn pub_fn(&mut self, sig: impl AsRef, cb: impl FnOnce(&mut CodeWriter)) { + self.expr_block(format!("pub fn {}", sig.as_ref()), cb) + } + + pub fn def_fn(&mut self, sig: impl AsRef, cb: impl FnOnce(&mut CodeWriter)) { + self.expr_block(format!("fn {}", sig.as_ref()), cb) + } + + pub fn pub_struct(&mut self, name: impl AsRef, cb: impl FnOnce(&mut CodeWriter)) { + self.expr_block(format!("pub struct {}", name.as_ref()), cb); + } + + pub fn field_decl(&mut self, name: impl AsRef, field_type: impl AsRef) { + self.write_line(format!("{}: {},", name.as_ref(), field_type.as_ref())); + } + + pub fn impl_self_block(&mut self, name: impl AsRef, cb: impl FnOnce(&mut CodeWriter)) { + self.expr_block(format!("impl {}", name.as_ref()), cb); + } + + pub fn pub_trait(&mut self, name: impl AsRef, cb: impl FnOnce(&mut CodeWriter)) { + self.expr_block(format!("pub trait {}", name.as_ref()), cb); + } +} diff --git a/example/Cargo.toml b/example/Cargo.toml index 64116403..1bb96195 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -9,9 +9,10 @@ readme = "README.md" repository = "https://github.com/alipay/ttrpc-rust" homepage = "https://github.com/alipay/ttrpc-rust" description = "An example of ttrpc." +publish = false [dev-dependencies] -protobuf = "3.1.0" +protobuf = "3.7.2" bytes = "0.4.11" libc = "0.2.79" byteorder = "1.3.2" @@ -51,7 +52,3 @@ path = "./async-stream-client.rs" [build-dependencies] ttrpc-codegen = { path = "../ttrpc-codegen"} - -[patch.crates-io] -ttrpc-compiler = { path = "../compiler"} - diff --git a/example/protocols/mod.rs b/example/protocols/mod.rs index 7c7af6c8..ded83f71 100644 --- a/example/protocols/mod.rs +++ b/example/protocols/mod.rs @@ -2,5 +2,9 @@ // // SPDX-License-Identifier: Apache-2.0 // + +#[rustfmt::skip] pub mod asynchronous; + +#[rustfmt::skip] pub mod sync; diff --git a/ttrpc-codegen/Cargo.toml b/ttrpc-codegen/Cargo.toml index 55f74624..d248836f 100644 --- a/ttrpc-codegen/Cargo.toml +++ b/ttrpc-codegen/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "ttrpc-codegen" version = "0.5.0" -edition = "2018" -authors = ["The AntFin Kata Team "] -license = "Apache-2.0" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } keywords = ["codegen", "ttrpc", "protobuf"] description = "Rust codegen for ttrpc using ttrpc-compiler crate" categories = ["network-programming", "development-tools::build-utils"] @@ -15,7 +15,7 @@ readme = "README.md" [dependencies] # lock home to avoid conflict with latest version home = "=0.5.9" -protobuf-support = "3.2.0" -protobuf = { version = "2.27.1" } -protobuf-codegen = "3.5.1" -ttrpc-compiler = "0.7.0" +protobuf-support = { workspace = true } +protobuf = { workspace = true } +protobuf-codegen = { workspace = true } +ttrpc-compiler = { workspace = true } diff --git a/ttrpc-codegen/src/convert.rs b/ttrpc-codegen/src/convert.rs index e819cafa..be3dec24 100644 --- a/ttrpc-codegen/src/convert.rs +++ b/ttrpc-codegen/src/convert.rs @@ -56,10 +56,12 @@ enum MessageOrEnum { } impl MessageOrEnum { - fn descriptor_type(&self) -> protobuf::descriptor::FieldDescriptorProto_Type { + fn descriptor_type(&self) -> protobuf::descriptor::field_descriptor_proto::Type { match *self { - MessageOrEnum::Message => protobuf::descriptor::FieldDescriptorProto_Type::TYPE_MESSAGE, - MessageOrEnum::Enum => protobuf::descriptor::FieldDescriptorProto_Type::TYPE_ENUM, + MessageOrEnum::Message => { + protobuf::descriptor::field_descriptor_proto::Type::TYPE_MESSAGE + } + MessageOrEnum::Enum => protobuf::descriptor::field_descriptor_proto::Type::TYPE_ENUM, } } } @@ -407,7 +409,7 @@ impl<'a> Resolver<'a> { output.set_number(number); let (t, t_name) = self.field_type(name, field_type, path_in_file); - output.set_field_type(t); + output.set_type(t); if let Some(t_name) = t_name { output.set_type_name(t_name.path); } @@ -424,13 +426,13 @@ impl<'a> Resolver<'a> { ) -> ConvertResult { let mut output = protobuf::descriptor::DescriptorProto::new(); - output.mut_options().set_map_entry(true); + output.options.mut_or_insert_default().set_map_entry(true); output.set_name(Resolver::map_entry_name_for_field_name(field_name)); output - .mut_field() + .field .push(self.map_entry_field("key", 1, key, path_in_file)); output - .mut_field() + .field .push(self.map_entry_field("value", 2, value, path_in_file)); Ok(output) @@ -459,49 +461,49 @@ impl<'a> Resolver<'a> { let mut output = protobuf::descriptor::DescriptorProto::new(); output.set_name(input.name.clone()); - let mut nested_messages = protobuf::RepeatedField::new(); - for m in &input.messages { - nested_messages.push(self.message(m, &nested_path_in_file)?); + output + .nested_type + .push(self.message(m, &nested_path_in_file)?); } for f in &input.fields { if let model::FieldType::Map(ref t) = f.typ { - nested_messages.push(self.map_entry_message(&f.name, &t.0, &t.1, path_in_file)?); + output.nested_type.push(self.map_entry_message( + &f.name, + &t.0, + &t.1, + path_in_file, + )?); } } - output.set_nested_type(nested_messages); - - output.set_enum_type( - input - .enums - .iter() - .map(|e| self.enumeration(e)) - .collect::>()?, - ); + output.enum_type = input + .enums + .iter() + .map(|e| self.enumeration(e)) + .collect::>()?; { - let mut fields = protobuf::RepeatedField::new(); - for f in &input.fields { - fields.push(self.field(f, None, &nested_path_in_file)?); + output + .field + .push(self.field(f, None, &nested_path_in_file)?); } for (oneof_index, oneof) in input.oneofs.iter().enumerate() { let oneof_index = oneof_index as i32; for f in &oneof.fields { - fields.push(self.field(f, Some(oneof_index), &nested_path_in_file)?); + output + .field + .push(self.field(f, Some(oneof_index), &nested_path_in_file)?); } } - - output.set_field(fields); } - let oneofs = input.oneofs.iter().map(|o| self.oneof(o)).collect(); - output.set_oneof_decl(oneofs); + output.oneof_decl = input.oneofs.iter().map(|o| self.oneof(o)).collect(); - output.set_options(self.message_options(&input.options)?); + *output.options.mut_or_insert_default() = self.message_options(&input.options)?; Ok(output) } @@ -540,7 +542,6 @@ impl<'a> Resolver<'a> { let mut output = protobuf::descriptor::ServiceDescriptorProto::new(); output.set_name(input.name.clone()); - let mut methods = protobuf::RepeatedField::new(); for m in &input.methods { let mut mm = protobuf::descriptor::MethodDescriptorProto::new(); mm.set_name(m.name.clone()); @@ -550,13 +551,12 @@ impl<'a> Resolver<'a> { mm.set_client_streaming(m.client_streaming); mm.set_server_streaming(m.server_streaming); - mm.set_options(self.method_options(&m.options)?); + *mm.options.mut_or_insert_default() = self.method_options(&m.options)?; - methods.push(mm); + output.method.push(mm); } - output.set_method(methods); - output.set_options(self.service_options(&input.options)?); + *output.options.mut_or_insert_default() = self.service_options(&input.options)?; Ok(output) } @@ -631,28 +631,28 @@ impl<'a> Resolver<'a> { output.set_name(input.name.clone()); if let model::FieldType::Map(..) = input.typ { - output.set_label(protobuf::descriptor::FieldDescriptorProto_Label::LABEL_REPEATED); + output.set_label(protobuf::descriptor::field_descriptor_proto::Label::LABEL_REPEATED); } else { output.set_label(label(input.rule)); } let (t, t_name) = self.field_type(&input.name, &input.typ, path_in_file); - output.set_field_type(t); + output.set_type(t); if let Some(t_name) = t_name { output.set_type_name(t_name.path); } output.set_number(input.number); if let Some(default) = input.options.as_slice().by_name("default") { - let default = match output.get_field_type() { - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_STRING => { + let default = match output.type_() { + protobuf::descriptor::field_descriptor_proto::Type::TYPE_STRING => { if let model::ProtobufConstant::String(ref s) = *default { s.decode_utf8()? } else { return Err(ConvertError::DefaultValueIsNotStringLiteral); } } - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_BYTES => { + protobuf::descriptor::field_descriptor_proto::Type::TYPE_BYTES => { if let model::ProtobufConstant::String(ref s) = *default { s.escaped.clone() } else { @@ -664,7 +664,7 @@ impl<'a> Resolver<'a> { output.set_default_value(default); } - output.set_options(self.field_options(&input.options)?); + *output.options.mut_or_insert_default() = self.field_options(&input.options)?; if let Some(oneof_index) = oneof_index { output.set_oneof_index(oneof_index); @@ -736,68 +736,68 @@ impl<'a> Resolver<'a> { input: &model::FieldType, path_in_file: &RelativePath, ) -> ( - protobuf::descriptor::FieldDescriptorProto_Type, + protobuf::descriptor::field_descriptor_proto::Type, Option, ) { match *input { model::FieldType::Bool => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_BOOL, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_BOOL, None, ), model::FieldType::Int32 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_INT32, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_INT32, None, ), model::FieldType::Int64 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_INT64, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_INT64, None, ), model::FieldType::Uint32 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_UINT32, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_UINT32, None, ), model::FieldType::Uint64 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_UINT64, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_UINT64, None, ), model::FieldType::Sint32 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_SINT32, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_SINT32, None, ), model::FieldType::Sint64 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_SINT64, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_SINT64, None, ), model::FieldType::Fixed32 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_FIXED32, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_FIXED32, None, ), model::FieldType::Fixed64 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_FIXED64, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_FIXED64, None, ), model::FieldType::Sfixed32 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_SFIXED32, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_SFIXED32, None, ), model::FieldType::Sfixed64 => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_SFIXED64, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_SFIXED64, None, ), model::FieldType::Float => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_FLOAT, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_FLOAT, None, ), model::FieldType::Double => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_DOUBLE, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_DOUBLE, None, ), model::FieldType::String => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_STRING, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_STRING, None, ), model::FieldType::Bytes => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_BYTES, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_BYTES, None, ), model::FieldType::MessageOrEnum(ref name) => { @@ -809,12 +809,12 @@ impl<'a> Resolver<'a> { type_name.push_relative(path_in_file); type_name.push_simple(&Resolver::map_entry_name_for_field_name(name)); ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_MESSAGE, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_MESSAGE, Some(type_name), ) } model::FieldType::Group(..) => ( - protobuf::descriptor::FieldDescriptorProto_Type::TYPE_GROUP, + protobuf::descriptor::field_descriptor_proto::Type::TYPE_GROUP, None, ), } @@ -852,14 +852,12 @@ impl<'a> Resolver<'a> { ) -> ConvertResult { let mut output = protobuf::descriptor::EnumDescriptorProto::new(); output.set_name(input.name.clone()); - output.set_value( - input - .values - .iter() - .map(|v| self.enum_value(&v.name, v.number)) - .collect(), - ); - output.set_options(self.enum_options(&input.options)?); + output.value = input + .values + .iter() + .map(|v| self.enum_value(&v.name, v.number)) + .collect(); + *output.options.mut_or_insert_default() = self.enum_options(&input.options)?; Ok(output) } @@ -1008,11 +1006,17 @@ fn syntax(input: model::Syntax) -> String { } } -fn label(input: model::Rule) -> protobuf::descriptor::FieldDescriptorProto_Label { +fn label(input: model::Rule) -> protobuf::descriptor::field_descriptor_proto::Label { match input { - model::Rule::Optional => protobuf::descriptor::FieldDescriptorProto_Label::LABEL_OPTIONAL, - model::Rule::Required => protobuf::descriptor::FieldDescriptorProto_Label::LABEL_REQUIRED, - model::Rule::Repeated => protobuf::descriptor::FieldDescriptorProto_Label::LABEL_REPEATED, + model::Rule::Optional => { + protobuf::descriptor::field_descriptor_proto::Label::LABEL_OPTIONAL + } + model::Rule::Required => { + protobuf::descriptor::field_descriptor_proto::Label::LABEL_REQUIRED + } + model::Rule::Repeated => { + protobuf::descriptor::field_descriptor_proto::Label::LABEL_REPEATED + } } } @@ -1031,35 +1035,27 @@ pub fn file_descriptor( output.set_package(input.package.clone()); output.set_syntax(syntax(input.syntax)); - let mut messages = protobuf::RepeatedField::new(); for m in &input.messages { - messages.push(resolver.message(m, &RelativePath::empty())?); + output + .message_type + .push(resolver.message(m, &RelativePath::empty())?); } - output.set_message_type(messages); - - let mut services = protobuf::RepeatedField::new(); for s in &input.services { - services.push(resolver.service(s, &input.package)?); + output.service.push(resolver.service(s, &input.package)?); } - output.set_service(services); - - output.set_enum_type( - input - .enums - .iter() - .map(|e| resolver.enumeration(e)) - .collect::>()?, - ); + output.enum_type = input + .enums + .iter() + .map(|e| resolver.enumeration(e)) + .collect::>()?; - output.set_options(resolver.file_options(&input.options)?); + *output.options.mut_or_insert_default() = resolver.file_options(&input.options)?; - let mut extensions = protobuf::RepeatedField::new(); for e in &input.extensions { - extensions.push(resolver.extension(e)?); + output.extension.push(resolver.extension(e)?); } - output.set_extension(extensions); Ok(output) } diff --git a/ttrpc-codegen/src/lib.rs b/ttrpc-codegen/src/lib.rs index 5a63b22e..a198a9a2 100644 --- a/ttrpc-codegen/src/lib.rs +++ b/ttrpc-codegen/src/lib.rs @@ -156,7 +156,7 @@ impl Codegen { } ttrpc_compiler::codegen::gen_and_write( - &p.file_descriptors, + p.file_descriptors.as_slice(), &p.relative_paths, &dst_path, &self.customize,