diff --git a/Cargo.lock b/Cargo.lock index fbad1c5c8..a51479339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -832,6 +832,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "quote" version = "1.0.45" @@ -945,6 +965,7 @@ version = "0.0.0" dependencies = [ "error_report", "ffi_types", + "protobuf", "rs_bindings_from_cc_database", "rs_bindings_from_cc_generate_bindings", ] @@ -1659,13 +1680,33 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1632ec01f..1eda4339c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ indenter = "0.3" once_cell = "1.21.3" phf = { version = "0.11", features = [ "macros" ] } proc-macro2 = "1" +protobuf = "3.2.0" quote = "1" regex = "1" rustversion = "1" diff --git a/cargo/rs_bindings_from_cc/generate_bindings/cc_api/Cargo.toml b/cargo/rs_bindings_from_cc/generate_bindings/cc_api/Cargo.toml index 38c7e7e54..dfa7c0849 100644 --- a/cargo/rs_bindings_from_cc/generate_bindings/cc_api/Cargo.toml +++ b/cargo/rs_bindings_from_cc/generate_bindings/cc_api/Cargo.toml @@ -22,3 +22,4 @@ generate_bindings = { path = "../../../../cargo/rs_bindings_from_cc/generate_bin error_report = { path = "../../../../cargo/common/error_report"} ffi_types = { path = "../../../../cargo/common/ffi_types"} database = { path = "../../../../cargo/rs_bindings_from_cc/generate_bindings/database/database", package = "rs_bindings_from_cc_database"} +protobuf.workspace = true diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD index cfa4ef3f5..7a8c6d415 100644 --- a/rs_bindings_from_cc/BUILD +++ b/rs_bindings_from_cc/BUILD @@ -521,8 +521,8 @@ cc_library( deps = [ ":cc_ir", "//common:cc_ffi_types", - "//common:status_macros", "//rs_bindings_from_cc/generate_bindings:cc_api", # buildcleaner: keep + "//rs_bindings_from_cc/generate_bindings:generate_bindings_cc_proto", "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", "@abseil-cpp//absl/strings:string_view", diff --git a/rs_bindings_from_cc/generate_bindings/BUILD b/rs_bindings_from_cc/generate_bindings/BUILD index 71581372f..b5b859e25 100644 --- a/rs_bindings_from_cc/generate_bindings/BUILD +++ b/rs_bindings_from_cc/generate_bindings/BUILD @@ -1,4 +1,6 @@ +load("@protobuf//bazel:cc_proto_library.bzl", "cc_proto_library") load("@protobuf//bazel:proto_library.bzl", "proto_library") +load("@protobuf//rust:defs.bzl", "rust_proto_library") load( "@rules_rust//rust:defs.bzl", "rust_library", @@ -15,6 +17,18 @@ proto_library( visibility = ["//visibility:public"], ) +cc_proto_library( + name = "generate_bindings_cc_proto", + visibility = ["//visibility:public"], + deps = [":generate_bindings_proto"], +) + +rust_proto_library( + name = "generate_bindings_rust_proto", + visibility = ["//visibility:public"], + deps = [":generate_bindings_proto"], +) + rust_library( name = "cc_api", srcs = [ @@ -26,9 +40,11 @@ rust_library( ], deps = [ ":generate_bindings", + ":generate_bindings_rust_proto", "//common:error_report", "//common:ffi_types", "//rs_bindings_from_cc/generate_bindings/database", + "@protobuf//rust:protobuf", ], ) diff --git a/rs_bindings_from_cc/generate_bindings/cc_api.rs b/rs_bindings_from_cc/generate_bindings/cc_api.rs index dfb90e2f6..48f4e295d 100644 --- a/rs_bindings_from_cc/generate_bindings/cc_api.rs +++ b/rs_bindings_from_cc/generate_bindings/cc_api.rs @@ -2,94 +2,105 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -use database::code_snippet::{Bindings, FfiBindings}; +use database::code_snippet::Bindings; use error_report::{ErrorReport, ErrorReporting, FatalErrors, SourceLanguage}; -use ffi_types::{Environment, FfiU8Slice, FfiU8SliceBox}; +use ffi_types::Environment; use generate_bindings::generate_bindings; -use std::ffi::OsString; +use generate_bindings_rust_proto::{GenerateBindingsRequestView, GenerateBindingsResponseMut}; +use protobuf::{MessageMutInterop, MessageViewInterop}; +use std::ffi::{c_void, OsString}; use std::panic::catch_unwind; use std::process; +use std::sync::Once; -/// Deserializes IR from `json` and generates bindings source code. -/// -/// This function aborts on error. +/// Deserializes IR from `request` and generates bindings source code +/// into the `response` object. +pub fn generate_bindings_impl( + request: GenerateBindingsRequestView<'_>, + mut response: GenerateBindingsResponseMut<'_>, +) { + + let json: &[u8] = request.json().as_bytes(); + let crubit_support_path_format: &str = request + .crubit_support_path_format() + .to_str() + .expect("crubit_support_path_format is not valid UTF-8"); + let clang_format_exe_path: OsString = request + .clang_format_exe_path() + .to_str() + .expect("clang_format_exe_path is not valid UTF-8") + .into(); + let rustfmt_exe_path: OsString = + request.rustfmt_exe_path().to_str().expect("rustfmt_exe_path is not valid UTF-8").into(); + let rustfmt_config_path: OsString = request + .rustfmt_config_path() + .to_str() + .expect("rustfmt_config_path is not valid UTF-8") + .into(); + let kythe_default_corpus: &str = + request.kythe_default_corpus().to_str().expect("kythe_default_corpus is not valid UTF-8"); + let generate_error_report: bool = request.generate_error_report(); + let environment = if request.skip_source_location_in_doc_comments() { + Environment::GoldenTest + } else { + Environment::Production + }; + let kythe_annotations: bool = request.kythe_annotations(); + + let mut error_report: Option = None; + let errors: &dyn ErrorReporting = if generate_error_report { + error_report.insert(ErrorReport::new(SourceLanguage::Cpp)) + } else { + &error_report::IgnoreErrors + }; + let fatal_errors = FatalErrors::new(); + let Bindings { rs_api, rs_api_impl } = generate_bindings( + json, + crubit_support_path_format, + &clang_format_exe_path, + &rustfmt_exe_path, + &rustfmt_config_path, + errors, + &fatal_errors, + environment, + kythe_annotations, + kythe_default_corpus, + ) + .unwrap(); + + response.set_rs_api(rs_api); + response.set_rs_api_impl(rs_api_impl); + + if let Some(err) = error_report.map(|s| s.to_json_string()) { + response.set_error_report(err); + } + + response.set_fatal_errors(fatal_errors.take_string()); +} + +/// C API for `generate_bindings_impl`. /// /// # Safety /// /// Expectations: -/// * `json` should be a FfiU8Slice for a valid array of bytes with the given -/// size. -/// * `crubit_support_path_format` should be a FfiU8Slice for a valid array -/// of bytes representing an UTF8-encoded string -/// * `rustfmt_exe_path` and `rustfmt_config_path` should both be a -/// FfiU8Slice for a valid array of bytes representing an UTF8-encoded -/// string (without the UTF-8 requirement, it seems that Rust doesn't offer -/// a way to convert to OsString on Windows) -/// * `json`, `crubit_support_path_format`, `rustfmt_exe_path`, and -/// `rustfmt_config_path` shouldn't change during the call. +/// * `raw_request` must point to a valid C++ `GenerateBindingsRequest` object. +/// * `raw_response` must point to a valid C++ `GenerateBindingsResponse` object. /// /// Ownership: -/// * function doesn't take ownership of (in other words it borrows) the -/// input params: `json`, `crubit_support_path_format`, `rustfmt_exe_path`, -/// and `rustfmt_config_path` -/// * function passes ownership of the returned value to the caller +/// * The function does not take ownership of the `raw_request` and `raw_response` pointers. +/// * The caller is responsible for their memory. #[unsafe(no_mangle)] pub unsafe extern "C" fn GenerateBindingsImpl( - json: FfiU8Slice, - crubit_support_path_format: FfiU8Slice, - clang_format_exe_path: FfiU8Slice, - rustfmt_exe_path: FfiU8Slice, - rustfmt_config_path: FfiU8Slice, - generate_error_report: bool, - environment: Environment, - kythe_annotations: bool, - kythe_default_corpus: FfiU8Slice, -) -> FfiBindings { - let json: &[u8] = json.as_slice(); - let crubit_support_path_format: &str = - std::str::from_utf8(crubit_support_path_format.as_slice()).unwrap(); - let clang_format_exe_path: OsString = - std::str::from_utf8(clang_format_exe_path.as_slice()).unwrap().into(); - let rustfmt_exe_path: OsString = - std::str::from_utf8(rustfmt_exe_path.as_slice()).unwrap().into(); - let rustfmt_config_path: OsString = - std::str::from_utf8(rustfmt_config_path.as_slice()).unwrap().into(); - let kythe_default_corpus: &str = std::str::from_utf8(kythe_default_corpus.as_slice()).unwrap(); - catch_unwind(|| { - let mut error_report: Option = None; - let errors: &dyn ErrorReporting = if generate_error_report { - error_report.insert(ErrorReport::new(SourceLanguage::Cpp)) - } else { - &error_report::IgnoreErrors + raw_request: *const c_void, + mut raw_response: *mut c_void, +) { + catch_unwind(std::panic::AssertUnwindSafe(|| { + let request = + unsafe { GenerateBindingsRequestView::__unstable_wrap_raw_message(&raw_request) }; + let response = unsafe { + GenerateBindingsResponseMut::__unstable_wrap_raw_message_mut(&mut raw_response) }; - let fatal_errors = FatalErrors::new(); - let Bindings { rs_api, rs_api_impl } = generate_bindings( - json, - crubit_support_path_format, - &clang_format_exe_path, - &rustfmt_exe_path, - &rustfmt_config_path, - errors, - &fatal_errors, - environment, - kythe_annotations, - kythe_default_corpus, - ) - .unwrap(); - FfiBindings { - rs_api: FfiU8SliceBox::from_boxed_slice(rs_api.into_bytes().into_boxed_slice()), - rs_api_impl: FfiU8SliceBox::from_boxed_slice( - rs_api_impl.into_bytes().into_boxed_slice(), - ), - error_report: FfiU8SliceBox::from_boxed_slice( - error_report - .map(|s| s.to_json_string().into_bytes().into_boxed_slice()) - .unwrap_or_else(|| Box::new([])), - ), - fatal_errors: FfiU8SliceBox::from_boxed_slice( - fatal_errors.take_string().into_bytes().into_boxed_slice(), - ), - } - }) - .unwrap_or_else(|_| process::abort()) + generate_bindings_impl(request, response); + })) + .unwrap_or_else(|_| process::abort()); } diff --git a/rs_bindings_from_cc/generate_bindings/generate_bindings.proto b/rs_bindings_from_cc/generate_bindings/generate_bindings.proto index 7070e45d5..19b61845c 100644 --- a/rs_bindings_from_cc/generate_bindings/generate_bindings.proto +++ b/rs_bindings_from_cc/generate_bindings/generate_bindings.proto @@ -4,16 +4,20 @@ edition = "2024"; -package rs_bindings_from_cc.generate_bindings; +package crubit.rs_bindings_from_cc.generate_bindings; // Parameters for invoking the Rust (backend) binding generator. message GenerateBindingsRequest { // Serialized JSON representation of the intermediate representation // produced by the C++ importer. string json = 1; + // Format string for the path to the Crubit support libraries. string crubit_support_path_format = 2; + // Path to the clang-format executable. string clang_format_exe_path = 3; + // Path to the rustfmt executable. string rustfmt_exe_path = 4; + // Path to the rustfmt configuration file. string rustfmt_config_path = 5; // When true, the generator will populate the `error_report` field in the // response with a JSON-formatted diagnostic report. diff --git a/rs_bindings_from_cc/src_code_gen.cc b/rs_bindings_from_cc/src_code_gen.cc index b584d8d7a..0236cc91c 100644 --- a/rs_bindings_from_cc/src_code_gen.cc +++ b/rs_bindings_from_cc/src_code_gen.cc @@ -10,55 +10,22 @@ #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "common/ffi_types.h" -#include "common/status_macros.h" +#include "rs_bindings_from_cc/generate_bindings/generate_bindings.pb.h" #include "rs_bindings_from_cc/ir.h" #include "llvm/Support/FormatVariadic.h" namespace crubit { -// FFI equivalent of `Bindings`. -struct FfiBindings { - FfiU8SliceBox rs_api; - FfiU8SliceBox rs_api_impl; - FfiU8SliceBox error_report; - FfiU8SliceBox fatal_errors; -}; +namespace { -// This function is implemented in Rust. -extern "C" FfiBindings GenerateBindingsImpl( - FfiU8Slice json, FfiU8Slice crubit_support_path_format, - FfiU8Slice clang_format_exe_path, FfiU8Slice rustfmt_exe_path, - FfiU8Slice rustfmt_config_path, bool generate_error_report, - Environment environment, bool kythe_annotations, - FfiU8Slice kythe_default_corpus); - -// Creates `Bindings` instance from copied data from `ffi_bindings`. -static absl::StatusOr MakeBindingsFromFfiBindings( - const FfiBindings& ffi_bindings) { - Bindings bindings; - - const FfiU8SliceBox& fatal_errors = ffi_bindings.fatal_errors; - std::string fatal_errors_string(fatal_errors.ptr, fatal_errors.size); - if (!fatal_errors_string.empty()) { - return absl::InvalidArgumentError(fatal_errors_string); - } - - const FfiU8SliceBox& rs_api = ffi_bindings.rs_api; - const FfiU8SliceBox& rs_api_impl = ffi_bindings.rs_api_impl; - const FfiU8SliceBox& error_report = ffi_bindings.error_report; +using rs_bindings_from_cc::generate_bindings::GenerateBindingsRequest; +using rs_bindings_from_cc::generate_bindings::GenerateBindingsResponse; - bindings.rs_api = std::string(rs_api.ptr, rs_api.size); - bindings.rs_api_impl = std::string(rs_api_impl.ptr, rs_api_impl.size); - bindings.error_report = std::string(error_report.ptr, error_report.size); - return bindings; -} +} // namespace -// Deallocates given `ffi_bindings` instance that was created in Rust. -static void FreeFfiBindings(FfiBindings ffi_bindings) { - FreeFfiU8SliceBox(ffi_bindings.rs_api); - FreeFfiU8SliceBox(ffi_bindings.rs_api_impl); - FreeFfiU8SliceBox(ffi_bindings.error_report); -} +// This function is implemented in Rust. +extern "C" void GenerateBindingsImpl(const GenerateBindingsRequest* request, + GenerateBindingsResponse* response); absl::StatusOr GenerateBindings( const IR& ir, absl::string_view crubit_support_path_format, @@ -66,15 +33,29 @@ absl::StatusOr GenerateBindings( absl::string_view rustfmt_config_path, bool generate_error_report, Environment environment, bool kythe_annotations, absl::string_view kythe_default_corpus) { - std::string json = llvm::formatv("{0}", ir.ToJson()); - FfiBindings ffi_bindings = GenerateBindingsImpl( - MakeFfiU8Slice(json), MakeFfiU8Slice(crubit_support_path_format), - MakeFfiU8Slice(clang_format_exe_path), MakeFfiU8Slice(rustfmt_exe_path), - MakeFfiU8Slice(rustfmt_config_path), generate_error_report, environment, - kythe_annotations, MakeFfiU8Slice(kythe_default_corpus)); - CRUBIT_ASSIGN_OR_RETURN(Bindings bindings, - MakeBindingsFromFfiBindings(ffi_bindings)); - FreeFfiBindings(ffi_bindings); + GenerateBindingsRequest request; + request.set_json(llvm::formatv("{0}", ir.ToJson())); + request.set_crubit_support_path_format(crubit_support_path_format); + request.set_clang_format_exe_path(clang_format_exe_path); + request.set_rustfmt_exe_path(rustfmt_exe_path); + request.set_rustfmt_config_path(rustfmt_config_path); + request.set_generate_error_report(generate_error_report); + request.set_skip_source_location_in_doc_comments(environment == + Environment::GoldenTest); + request.set_kythe_annotations(kythe_annotations); + request.set_kythe_default_corpus(kythe_default_corpus); + + GenerateBindingsResponse response; + GenerateBindingsImpl(&request, &response); + + if (!response.fatal_errors().empty()) { + return absl::InvalidArgumentError(response.fatal_errors()); + } + + Bindings bindings; + bindings.rs_api = response.rs_api(); + bindings.rs_api_impl = response.rs_api_impl(); + bindings.error_report = response.error_report(); return bindings; }