Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate Rust to C++ FFI #4220

Merged
merged 24 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from 13 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ swift/wallet-core/
codegen-v2/bindings/

src/Generated/*.cpp
include/Generated/TrustWalletCore/*.h
include/TrustWalletCore/TWHRP.h
include/TrustWalletCore/TW*Proto.h
include/TrustWalletCore/TWEthereumChainID.h
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ endif ()
target_include_directories(TrustWalletCore
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/Generated>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
Expand Down
54 changes: 47 additions & 7 deletions codegen-v2/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codegen-v2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ serde_yaml = "0.9.21"
toml_edit = "0.21.0"
handlebars = "4.3.6"
heck = "0.4.1"
tw_macros = { path = "../rust/tw_macros" }
tw_misc = { path = "../rust/tw_misc", features = ["serde"] }
gupnik marked this conversation as resolved.
Show resolved Hide resolved
166 changes: 166 additions & 0 deletions codegen-v2/src/codegen/cpp_v2/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use std::fs;
use std::io::Write;
use tw_misc::code_gen::TWConfig;

use crate::Result;
fn map_type(ty: &str) -> String {
match ty.trim() {
s if s.starts_with("* const") => format!("{} *_Nonnull", &s[8..]),
s if s.starts_with("* mut") => format!("{} *_Nullable", &s[6..]),
"bool" => "bool".to_string(),
_ => ty.to_string(),
}
}

fn generate_conversion_code(ty: &str, name: &str) -> (String, String) {
match ty {
"TWString *_Nonnull" => {
let code = format!(
" auto& {name}String = *reinterpret_cast<const std::string*>({name});\n",
name = name
) + format!(
" const Rust::TWStringWrapper {name}RustStr = {name}String;\n",
name = name
)
.as_str();
(code, format!("{}RustStr.get()", name))
}
_ => ("".to_string(), name.to_string()),
}
}

pub fn generate_cpp_bindings() -> Result<()> {
gupnik marked this conversation as resolved.
Show resolved Hide resolved
const IN_DIR: &str = "../rust/bindings/";
const HEADER_OUT_DIR: &str = "../include/Generated/TrustWalletCore/";
const SOURCE_OUT_DIR: &str = "../src/Generated/";

std::fs::create_dir_all(HEADER_OUT_DIR)?;
std::fs::create_dir_all(SOURCE_OUT_DIR)?;

let entries = fs::read_dir(IN_DIR)?;

for entry in entries {
let entry = entry?;
let file_path = entry.path();

if file_path.is_dir() {
println!("Found unexpected directory: {}", file_path.display());
continue;
}

let file_contents = fs::read_to_string(&file_path)?;

let info: TWConfig =
serde_yaml::from_str(&file_contents).expect("Failed to parse YAML file");

let file_path = format!("{HEADER_OUT_DIR}/{}.h", info.class);

let mut file = std::fs::File::create(&file_path)?;
writeln!(file, "// Copyright © 2017 Trust Wallet.\n")?;
writeln!(file, "#pragma once\n")?;
writeln!(file, "#include <TrustWalletCore/TWBase.h>")?;

// Include headers based on argument types
let mut included_headers = std::collections::HashSet::new();
for func in &info.static_functions {
for arg in &func.args {
if arg.ty.contains("TWString") && included_headers.insert("TWString.h") {
writeln!(file, "#include <TrustWalletCore/TWString.h>")?;
}
// Additional type checks can be added here in the future
}
}

writeln!(file, "\nTW_EXTERN_C_BEGIN\n")?;

let class_name = format!("{}", info.class);
let class_dec = format!("TW_EXPORT_CLASS\nstruct {};\n", class_name.clone());
writeln!(file, "{}", class_dec)?;

for func in &info.static_functions {
let return_type = map_type(&func.return_type);
gupnik marked this conversation as resolved.
Show resolved Hide resolved
let mut func_dec = format!(
"TW_EXPORT_STATIC_METHOD\n{} {}(",
return_type,
class_name.clone() + &func.name
);
for (i, arg) in func.args.iter().enumerate() {
let func_type = map_type(&arg.ty);
func_dec += format!("{} {}", func_type, arg.name).as_str();
if i < func.args.len() - 1 {
func_dec += ", ";
}
}
func_dec += ");\n";
writeln!(file, "{}", func_dec)?;
}

writeln!(file, "TW_EXTERN_C_END")?;

file.flush()?;

let file_path = format!("{SOURCE_OUT_DIR}/{}.cpp", info.class);

let mut file = std::fs::File::create(&file_path)?;
writeln!(file, "// Copyright © 2017 Trust Wallet.\n")?;
writeln!(
file,
"{}",
format!("#include <Generated/TrustWalletCore/{}.h>", info.class)
)?;
writeln!(file, "#include \"rust/Wrapper.h\"")?;

writeln!(file, "\nusing namespace TW;\n")?;

for func in &info.static_functions {
let return_type = map_type(&func.return_type);
let mut func_dec = format!("{} {}(", return_type, class_name.clone() + &func.name);
gupnik marked this conversation as resolved.
Show resolved Hide resolved
let mut conversion_code = vec![];
for (i, arg) in func.args.iter().enumerate() {
let func_type = map_type(&arg.ty);
let code_with_name = generate_conversion_code(&func_type, &arg.name);
conversion_code.push(code_with_name);
func_dec += format!("{} {}", func_type, arg.name).as_str();
if i < func.args.len() - 1 {
func_dec += ", ";
}
}
gupnik marked this conversation as resolved.
Show resolved Hide resolved
func_dec += ") {\n";
for code in &conversion_code {
func_dec += code.0.as_str();
}
if return_type == "TWString *_Nullable" {
func_dec += format!(
" const Rust::TWStringWrapper result = Rust::{}(",
func.rust_name
)
.as_str();
for (i, arg) in conversion_code.iter().enumerate() {
func_dec += format!("{}", arg.1).as_str();
if i < conversion_code.len() - 1 {
func_dec += ", ";
}
}
func_dec += ");\n";
func_dec += format!(" if (!result) return nullptr;\n").as_str();
func_dec +=
format!(" return TWStringCreateWithUTF8Bytes(result.c_str());\n").as_str();
gupnik marked this conversation as resolved.
Show resolved Hide resolved
} else {
func_dec += format!(" return Rust::{}(", func.rust_name).as_str();
for (i, arg) in conversion_code.iter().enumerate() {
func_dec += format!("{}", arg.1).as_str();
if i < conversion_code.len() - 1 {
func_dec += ", ";
}
}
func_dec += ");\n";
}
func_dec += "}\n";
writeln!(file, "{}", func_dec)?;
}

file.flush()?;
}

Ok(())
}
1 change: 1 addition & 0 deletions codegen-v2/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Copyright © 2017 Trust Wallet.

pub mod cpp;
pub mod cpp_v2;
gupnik marked this conversation as resolved.
Show resolved Hide resolved
pub mod proto;
pub mod rust;
pub mod swift;
Expand Down
9 changes: 8 additions & 1 deletion codegen-v2/src/codegen/template_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ impl TemplateGenerator {
.add_pattern("{TW_CRATE_NAME}", coin.id.to_tw_crate_name())
.add_pattern("{COIN_ID}", coin.id.as_str())
.add_pattern("{COIN_TYPE}", coin.coin_type())
.add_pattern("{COIN_NAME}", if coin.display_name.len() > 0 { &coin.display_name } else { &coin.name })
.add_pattern(
"{COIN_NAME}",
if coin.display_name.len() > 0 {
&coin.display_name
} else {
&coin.name
},
)
.add_pattern("{SYMBOL}", &coin.symbol)
.add_pattern("{DECIMALS}", coin.decimals)
.add_pattern("{P2PKH_PREFIX}", coin.p2pkh_prefix)
Expand Down
2 changes: 2 additions & 0 deletions codegen-v2/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// Copyright © 2017 Trust Wallet.

use libparser::codegen::cpp_v2::generate_cpp_bindings;
use libparser::codegen::swift::RenderIntput;
use libparser::codegen::{cpp, proto, rust};
use libparser::coin_id::CoinId;
Expand All @@ -23,6 +24,7 @@ fn main() -> Result<()> {
"new-evmchain" => new_evmchain(&args[2..]),
"new-cosmos-chain" => new_cosmos_chain(&args[2..]),
"swift" => generate_swift_bindings(),
"cpp" => generate_cpp_bindings(),
_ => Err(Error::InvalidCommand),
}
}
Expand Down
16 changes: 10 additions & 6 deletions codegen/bin/codegen
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Encoding.default_internal = Encoding::UTF_8
options = OpenStruct.new
# default input / output path
options.input = "#{CurrentDir}/../../include/TrustWalletCore"
options.input_generated = "#{CurrentDir}/../../include/Generated/TrustWalletCore"
options.output = "#{CurrentDir}/../../"
options.swift = true
options.java = true
Expand Down Expand Up @@ -62,14 +63,17 @@ end.parse!

entities = []
files = []
Dir.foreach(options.input) do |item|
next if ['.', '..', '.DS_Store'].include?(item)
[options.input, options.input_generated].each do |input_dir|
Dir.foreach(input_dir) do |item|
next if ['.', '..', '.DS_Store'].include?(item)

entity = Parser.new(path: File.expand_path(File.join(options.input, item))).parse
next if entity.nil?
file_path = File.expand_path(File.join(input_dir, item))
entity = Parser.new(path: file_path).parse
next if entity.nil?

entities << entity
files << File.basename(item, '.h').sub(/^TW/, '')
entities << entity
files << File.basename(item, '.h').sub(/^TW/, '')
end
end

generator = CodeGenerator.new(entities: entities, files: files, output_folder: options.output)
Expand Down
Loading
Loading