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

Initial refactor in preparing for WASM #174

Merged
merged 13 commits into from
Dec 10, 2024
Merged
21 changes: 18 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ jobs:
- name: Run tests
run: cargo test --verbose

integration-tests-generation:
name: 🧩 Integration tests (generation)
- name: Run tests for binaries
run: cargo test --bins

integration-tests-jsi-bindings:
name: 🧩 Integration tests (JSI bindings)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -58,9 +61,21 @@ jobs:
- name: Installing hermes and test-runner
run: cargo xtask bootstrap

- name: Run tests of generated bindings
- name: Run tests of generated JSI bindings
run: ./scripts/run-tests.sh

integration-tests-wasm-bindings:
name: 🧩 Integration tests (WASM bindings)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Installing
run: cargo xtask bootstrap yarn

- name: Run tests of generated WASM bindings
run: ./scripts/run-tests.sh --flavor wasm

integration-tests-checkout:
name: 🧩 Integration tests (checkout)
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions crates/ubrn_bindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = []
wasm = []

[dependencies]
anyhow = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/ubrn_bindgen/askama.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[general]
dirs = ["src/bindings/react_native/gen_typescript/templates", "src/bindings/react_native/gen_cpp/templates"]
dirs = ["src/bindings/gen_typescript/templates", "src/bindings/gen_cpp/templates"]

[[syntax]]
name = "ts"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,124 +3,22 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/
*/
mod gen_cpp;
mod gen_typescript;
mod uniffi_toml;
use std::collections::HashMap;

use std::{collections::HashMap, fs};

use anyhow::Result;
use camino::Utf8Path;
use extend::ext;
use heck::{ToLowerCamelCase, ToSnakeCase};
use topological_sort::TopologicalSort;
use ubrn_common::{fmt, run_cmd_quietly};
use uniffi_bindgen::{
interface::{
FfiArgument, FfiCallbackFunction, FfiDefinition, FfiField, FfiFunction, FfiStruct, FfiType,
Function, Method, Object, UniffiTrait,
},
BindingGenerator, Component, ComponentInterface, GenerationSettings,
ComponentInterface,
};
use uniffi_meta::Type;
use uniffi_toml::ReactNativeConfig;

use self::{gen_cpp::CppBindings, gen_typescript::TsBindings};

use super::{type_map::TypeMap, OutputArgs};
use crate::bindings::metadata::ModuleMetadata;

pub(crate) struct ReactNativeBindingGenerator {
output: OutputArgs,
}

impl ReactNativeBindingGenerator {
pub(crate) fn new(output: OutputArgs) -> Self {
Self { output }
}

pub(crate) fn format_code(&self) -> Result<()> {
format_ts(&self.output.ts_dir.canonicalize_utf8()?)?;
format_cpp(&self.output.cpp_dir.canonicalize_utf8()?)?;
Ok(())
}
}

impl BindingGenerator for ReactNativeBindingGenerator {
type Config = ReactNativeConfig;

fn new_config(&self, root_toml: &toml::value::Value) -> Result<Self::Config> {
Ok(match root_toml.get("bindings") {
Some(v) => v.clone().try_into()?,
None => Default::default(),
})
}

fn update_component_configs(
&self,
_settings: &GenerationSettings,
_components: &mut Vec<Component<Self::Config>>,
) -> Result<()> {
// NOOP
Ok(())
}

fn write_bindings(
&self,
settings: &GenerationSettings,
components: &[Component<Self::Config>],
) -> Result<()> {
let mut type_map = TypeMap::default();
for component in components {
type_map.insert_ci(&component.ci);
}
for component in components {
let ci = &component.ci;
let module: ModuleMetadata = component.into();
let config = &component.config;
let TsBindings { codegen, frontend } =
gen_typescript::generate_bindings(ci, &config.typescript, &module, &type_map)?;

let out_dir = &self.output.ts_dir.canonicalize_utf8()?;
let codegen_path = out_dir.join(module.ts_ffi_filename());
let frontend_path = out_dir.join(module.ts_filename());
fs::write(codegen_path, codegen)?;
fs::write(frontend_path, frontend)?;

let out_dir = &self.output.cpp_dir.canonicalize_utf8()?;
let CppBindings { hpp, cpp } = gen_cpp::generate_bindings(ci, &config.cpp, &module)?;
let cpp_path = out_dir.join(module.cpp_filename());
let hpp_path = out_dir.join(module.hpp_filename());
fs::write(cpp_path, cpp)?;
fs::write(hpp_path, hpp)?;
}
if settings.try_format_code {
self.format_code()?;
}
Ok(())
}
}

fn format_ts(out_dir: &Utf8Path) -> Result<()> {
if let Some(mut prettier) = fmt::prettier(out_dir, false)? {
run_cmd_quietly(&mut prettier)?
} else {
eprintln!("No prettier found. Install with `yarn add --dev prettier`");
}
Ok(())
}

fn format_cpp(out_dir: &Utf8Path) -> Result<()> {
if let Some(mut clang_format) = fmt::clang_format(out_dir, false)? {
run_cmd_quietly(&mut clang_format)?
} else {
eprintln!("Skipping formatting C++. Is clang-format installed?");
}
Ok(())
}

#[ext]
impl ComponentInterface {
pub(crate) impl ComponentInterface {
fn ffi_function_string_to_arraybuffer(&self) -> FfiFunction {
let meta = uniffi_meta::FnMetadata {
module_path: "internal".to_string(),
Expand Down Expand Up @@ -357,7 +255,7 @@ fn store_with_name(types: &mut HashMap<String, Type>, type_: &Type) -> String {
}

#[ext]
impl Object {
pub(crate) impl Object {
fn is_uniffi_trait(t: &UniffiTrait, nm: &str) -> bool {
match t {
UniffiTrait::Debug { .. } => nm == "Debug",
Expand Down Expand Up @@ -397,15 +295,15 @@ impl Object {
}

#[ext]
impl FfiFunction {
pub(crate) impl FfiFunction {
fn is_internal(&self) -> bool {
let name = self.name();
name.contains("ffi__") && name.contains("_internal_")
}
}

#[ext]
impl FfiDefinition {
pub(crate) impl FfiDefinition {
fn is_exported(&self) -> bool {
match self {
Self::Function(_) => false,
Expand All @@ -416,7 +314,7 @@ impl FfiDefinition {
}

#[ext]
impl FfiType {
pub(crate) impl FfiType {
fn is_callable(&self) -> bool {
matches!(self, Self::Callback(_))
}
Expand Down Expand Up @@ -458,14 +356,14 @@ impl FfiType {
}

#[ext]
impl FfiArgument {
pub(crate) impl FfiArgument {
fn is_return(&self) -> bool {
self.name() == "uniffi_out_return"
}
}

#[ext]
impl FfiCallbackFunction {
pub(crate) impl FfiCallbackFunction {
fn cpp_namespace(&self, ci: &ComponentInterface) -> String {
let ffi_type = FfiType::Callback(self.name().to_string());
ffi_type.cpp_namespace(ci)
Expand Down Expand Up @@ -516,7 +414,7 @@ fn is_free(nm: &str) -> bool {
}

#[ext]
impl FfiStruct {
pub(crate) impl FfiStruct {
fn cpp_namespace(&self, ci: &ComponentInterface) -> String {
let ffi_type = FfiType::Struct(self.name().to_string());
ffi_type.cpp_namespace(ci)
Expand Down Expand Up @@ -548,7 +446,7 @@ impl FfiStruct {
}

#[ext]
impl FfiField {
pub(crate) impl FfiField {
fn is_free(&self) -> bool {
matches!(self.type_(), FfiType::Callback(s) if is_free(&s))
}
Expand Down
11 changes: 11 additions & 0 deletions crates/ubrn_bindgen/src/bindings/gen_cpp/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/
*/

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CppConfig {}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use heck::{ToLowerCamelCase, ToUpperCamelCase};
use uniffi_bindgen::{interface::FfiType, ComponentInterface};

use crate::bindings::react_native::{ComponentInterfaceExt, FfiTypeExt};
use crate::bindings::extensions::{ComponentInterfaceExt, FfiTypeExt};

pub fn ffi_type_name_from_js(ffi_type: &FfiType) -> Result<String, askama::Error> {
Ok(match ffi_type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,40 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/
*/
mod config;
mod filters;
mod util;

use std::borrow::Borrow;

use crate::bindings::{
metadata::ModuleMetadata,
react_native::{
ComponentInterfaceExt, FfiArgumentExt, FfiCallbackFunctionExt, FfiFieldExt, FfiStructExt,
FfiTypeExt, ObjectExt,
},
};
use anyhow::Result;
use askama::Template;
use std::borrow::Borrow;
use uniffi_bindgen::interface::{FfiDefinition, FfiType};
use uniffi_bindgen::ComponentInterface;

type Config = crate::bindings::react_native::uniffi_toml::CppConfig;
pub(crate) use self::{config::CppConfig as Config, util::format_directory};
use crate::bindings::{
extensions::{
ComponentInterfaceExt, FfiArgumentExt, FfiCallbackFunctionExt, FfiFieldExt, FfiStructExt,
FfiTypeExt, ObjectExt,
},
metadata::ModuleMetadata,
};

#[derive(Debug, Default)]
pub(crate) struct CppBindings {
pub(crate) hpp: String,
pub(crate) cpp: String,
pub(crate) fn generate_hpp(
ci: &ComponentInterface,
config: &Config,
module: &ModuleMetadata,
) -> Result<String> {
Ok(HppWrapper::new(ci, config, module).render()?)
}

pub(crate) fn generate_bindings(
pub(crate) fn generate_cpp(
ci: &ComponentInterface,
config: &Config,
module: &ModuleMetadata,
) -> Result<CppBindings> {
let hpp = HppWrapper::new(ci, config, module).render()?;
let cpp = CppWrapper::new(ci, config, module).render()?;
Ok(CppBindings { hpp, cpp })
) -> Result<String> {
Ok(CppWrapper::new(ci, config, module).render()?)
}

#[derive(Template)]
Expand Down Expand Up @@ -65,3 +68,14 @@ impl<'a> CppWrapper<'a> {
Self { ci, config, module }
}
}

pub fn generate_entrypoint(modules: &Vec<ModuleMetadata>) -> Result<String> {
let index = EntrypointCpp { modules };
Ok(index.render()?)
}

#[derive(Template)]
#[template(path = "entrypoint.cpp", escape = "none")]
struct EntrypointCpp<'a> {
modules: &'a Vec<ModuleMetadata>,
}
17 changes: 17 additions & 0 deletions crates/ubrn_bindgen/src/bindings/gen_cpp/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/
*/
use anyhow::Result;
use camino::Utf8Path;
use ubrn_common::{fmt, run_cmd_quietly};

pub(crate) fn format_directory(out_dir: &Utf8Path) -> Result<()> {
if let Some(mut clang_format) = fmt::clang_format(out_dir, false)? {
run_cmd_quietly(&mut clang_format)?
} else {
eprintln!("Skipping formatting C++. Is clang-format installed?");
}
Ok(())
}
Loading
Loading