forked from mozilla/gecko-dev
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1766045 - Initial work for UniFFI JS bindings r=nika
Generate the C++ and JS code to handle UniFFI bindings. The WebIDL code is completely static and doesn't need to be generated. There's support for both synchronus and async functions, but we haven't decided the how we want this to be configured. In practice, almost all functions will need to be async, so for now we're just forcing all functions to be. The `uniffi-bindgen-gecko-js` crate builds the binary that generates the bindings. This binary needs to be fed a list of UDL files, the path of the .cpp file to generate, and the directory to generate .jsm files in (and also all of those arguments again, but for the test fixtures). This is quiet a horrible UI, but it's going to be wrapped in a mach command. The `uniffi-js` directory contains shared C++ code for `uniffi-bindgen-gecko-js`. As much as possible we tried to put the functionality here and have the generated code simply forward function calls here. Still Todo: - CallbackInterfaces - Custom and external types - Datetime and TimeInterval Differential Revision: https://phabricator.services.mozilla.com/D144472
- Loading branch information
Showing
56 changed files
with
4,101 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ | ||
/* 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/. */ | ||
|
||
// Interface for making UniFFI scaffolding calls | ||
// | ||
// Gecko uses UniFFI to generate privileged JS bindings for Rust components. | ||
// UniFFI defines a C-ABI FFI layer for calling into Rust, called the | ||
// scaffolding. This interface is a bridge that allows the JS code to make | ||
// scaffolding calls | ||
// | ||
// See https://mozilla.github.io/uniffi-rs/ for details. | ||
|
||
// Opaque type used to represent a pointer from Rust | ||
[ChromeOnly, Exposed=Window] | ||
interface UniFFIPointer {}; | ||
|
||
// Types that can be passed or returned from scaffolding functions | ||
// | ||
// - double is used for all numeric types (including bool, which the JS code | ||
// coerces to an int) | ||
// - ArrayBuffer is used for RustBuffer | ||
// - UniFFIPointer is used for Arc pointers | ||
typedef (double or ArrayBuffer or UniFFIPointer) UniFFIScaffoldingType; | ||
|
||
// The result of a call into UniFFI scaffolding call | ||
enum UniFFIScaffoldingCallCode { | ||
"success", // Successful return | ||
"error", // Rust Err return | ||
"internal-error", // Internal/unexpected error | ||
}; | ||
|
||
dictionary UniFFIScaffoldingCallResult { | ||
required UniFFIScaffoldingCallCode code; | ||
// For success, this will be the return value for non-void returns | ||
// For error, this will be an ArrayBuffer storing the serialized error value | ||
UniFFIScaffoldingType data; | ||
// For internal-error, this will be a utf-8 string describing the error | ||
ByteString internalErrorMessage; | ||
}; | ||
|
||
// Functions to facilitate UniFFI scaffolding calls | ||
// | ||
// These should only be called by the generated code from UniFFI. | ||
[ChromeOnly, Exposed=Window] | ||
namespace UniFFIScaffolding { | ||
[Throws] | ||
Promise<UniFFIScaffoldingCallResult> callAsync(unsigned long long id, UniFFIScaffoldingType... args); | ||
|
||
[Throws] | ||
UniFFIScaffoldingCallResult callSync(unsigned long long id, UniFFIScaffoldingType... args); | ||
|
||
[Throws] | ||
UniFFIPointer readPointer(unsigned long long id, ArrayBuffer buff, long position); | ||
|
||
[Throws] | ||
void writePointer(unsigned long long id, UniFFIPointer ptr, ArrayBuffer buff, long position); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "uniffi-bindgen-gecko-js" | ||
version = "0.1.0" | ||
edition = "2018" | ||
|
||
[[bin]] | ||
name = "uniffi-bindgen-gecko-js" | ||
path = "src/main.rs" | ||
|
||
[dependencies] | ||
anyhow = "1" | ||
askama = { version = "0.11", default-features = false, features = ["config"] } | ||
clap = { version = "3.1", features = ["std", "derive"] } | ||
extend = "1.1" | ||
heck = "0.4" | ||
uniffi_bindgen = "0.18" | ||
serde = "1" | ||
toml = "0.5" | ||
camino = "1.0.8" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[general] | ||
dirs = ["src/templates"] |
139 changes: 139 additions & 0 deletions
139
toolkit/components/uniffi-bindgen-gecko-js/src/ci_list.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/* 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/. */ | ||
|
||
//! Manage the universe of ComponentInterfaces | ||
//! | ||
//! uniffi-bindgen-gecko-js is unique because it generates bindings over a set of UDL files rather | ||
//! than just one. This is because we want to generate the WebIDL statically rather than generate | ||
//! it. To accomplish that, each WebIDL function inputs an opaque integer id that identifies which | ||
//! version of it should run, for example `CallSync` inputs a function id. Operating on all UDL | ||
//! files at once simplifies the task of ensuring those ids are to be unique and consistent between | ||
//! the JS and c++ code. | ||
//! | ||
//! This module manages the list of ComponentInterface and the object ids. | ||
use crate::render::cpp::ComponentInterfaceCppExt; | ||
use anyhow::{bail, Context, Result}; | ||
use camino::Utf8PathBuf; | ||
use std::collections::{BTreeSet, HashMap, HashSet}; | ||
use uniffi_bindgen::interface::{ComponentInterface, FFIFunction, Object}; | ||
|
||
pub struct ComponentInterfaceUniverse { | ||
ci_list: Vec<ComponentInterface>, | ||
fixture_ci_list: Vec<ComponentInterface>, | ||
} | ||
|
||
impl ComponentInterfaceUniverse { | ||
pub fn new(udl_files: Vec<Utf8PathBuf>, fixture_udl_files: Vec<Utf8PathBuf>) -> Result<Self> { | ||
let ci_list = udl_files | ||
.into_iter() | ||
.map(parse_udl_file) | ||
.collect::<Result<Vec<_>>>()?; | ||
let fixture_ci_list = fixture_udl_files | ||
.into_iter() | ||
.map(parse_udl_file) | ||
.collect::<Result<Vec<_>>>()?; | ||
Self::check_udl_namespaces_unique(&ci_list, &fixture_ci_list)?; | ||
Ok(Self { | ||
ci_list, | ||
fixture_ci_list, | ||
}) | ||
} | ||
|
||
fn check_udl_namespaces_unique( | ||
ci_list: &Vec<ComponentInterface>, | ||
fixture_ci_list: &Vec<ComponentInterface>, | ||
) -> Result<()> { | ||
let mut set = HashSet::new(); | ||
for ci in ci_list.iter().chain(fixture_ci_list.iter()) { | ||
if !set.insert(ci.namespace()) { | ||
bail!("UDL files have duplicate namespace: {}", ci.namespace()); | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
pub fn ci_list(&self) -> &Vec<ComponentInterface> { | ||
&self.ci_list | ||
} | ||
|
||
pub fn fixture_ci_list(&self) -> &Vec<ComponentInterface> { | ||
&self.fixture_ci_list | ||
} | ||
|
||
pub fn iter_all(&self) -> impl Iterator<Item = &ComponentInterface> { | ||
self.ci_list.iter().chain(self.fixture_ci_list.iter()) | ||
} | ||
} | ||
|
||
fn parse_udl_file(udl_file: Utf8PathBuf) -> Result<ComponentInterface> { | ||
let udl = std::fs::read_to_string(&udl_file).context("Error reading UDL file")?; | ||
ComponentInterface::from_webidl(&udl).context("Failed to parse UDL") | ||
} | ||
|
||
pub struct FunctionIds<'a> { | ||
// Map (CI namespace, func name) -> Ids | ||
map: HashMap<(&'a str, &'a str), usize>, | ||
} | ||
|
||
impl<'a> FunctionIds<'a> { | ||
pub fn new(cis: &'a ComponentInterfaceUniverse) -> Self { | ||
Self { | ||
map: cis | ||
.iter_all() | ||
.flat_map(|ci| { | ||
ci.exposed_functions() | ||
.into_iter() | ||
.map(move |f| (ci.namespace(), f.name())) | ||
}) | ||
.enumerate() | ||
.map(|(i, (namespace, name))| ((namespace, name), i)) | ||
// Sort using BTreeSet to guarantee the IDs remain stable across runs | ||
.collect::<BTreeSet<_>>() | ||
.into_iter() | ||
.collect(), | ||
} | ||
} | ||
|
||
pub fn get(&self, ci: &ComponentInterface, func: &FFIFunction) -> usize { | ||
return *self.map.get(&(ci.namespace(), func.name())).unwrap(); | ||
} | ||
|
||
pub fn name(&self, ci: &ComponentInterface, func: &FFIFunction) -> String { | ||
format!("{}:{}", ci.namespace(), func.name()) | ||
} | ||
} | ||
|
||
pub struct ObjectIds<'a> { | ||
// Map (CI namespace, object name) -> Ids | ||
map: HashMap<(&'a str, &'a str), usize>, | ||
} | ||
|
||
impl<'a> ObjectIds<'a> { | ||
pub fn new(cis: &'a ComponentInterfaceUniverse) -> Self { | ||
Self { | ||
map: cis | ||
.iter_all() | ||
.flat_map(|ci| { | ||
ci.object_definitions() | ||
.iter() | ||
.map(move |o| (ci.namespace(), o.name())) | ||
}) | ||
.enumerate() | ||
.map(|(i, (namespace, name))| ((namespace, name), i)) | ||
// Sort using BTreeSet to guarantee the IDs remain stable across runs | ||
.collect::<BTreeSet<_>>() | ||
.into_iter() | ||
.collect(), | ||
} | ||
} | ||
|
||
pub fn get(&self, ci: &ComponentInterface, obj: &Object) -> usize { | ||
return *self.map.get(&(ci.namespace(), obj.name())).unwrap(); | ||
} | ||
|
||
pub fn name(&self, ci: &ComponentInterface, obj: &Object) -> String { | ||
format!("{}:{}", ci.namespace(), obj.name()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* 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::{Context, Result}; | ||
use askama::Template; | ||
use camino::Utf8PathBuf; | ||
use clap::Parser; | ||
use heck::ToTitleCase; | ||
use std::fs::File; | ||
use std::io::Write; | ||
|
||
mod ci_list; | ||
mod render; | ||
|
||
pub use ci_list::{ComponentInterfaceUniverse, FunctionIds, ObjectIds}; | ||
pub use render::cpp::CPPScaffoldingTemplate; | ||
pub use render::js::JSBindingsTemplate; | ||
use uniffi_bindgen::ComponentInterface; | ||
|
||
#[derive(Debug, Parser)] | ||
#[clap(name = "uniffi-bindgen-gecko-js")] | ||
#[clap(version = clap::crate_version!())] | ||
#[clap(about = "JS bindings generator for Rust")] | ||
#[clap(propagate_version = true)] | ||
struct CliArgs { | ||
// This is a really convoluted set of arguments, but we're only expecting to be called by | ||
// `mach_commands.py` | ||
#[clap(long, value_name = "FILE")] | ||
js_dir: Utf8PathBuf, | ||
|
||
#[clap(long, value_name = "FILE")] | ||
fixture_js_dir: Utf8PathBuf, | ||
|
||
#[clap(long, value_name = "FILE")] | ||
cpp_path: Utf8PathBuf, | ||
|
||
#[clap(long, value_name = "FILE")] | ||
fixture_cpp_path: Utf8PathBuf, | ||
|
||
#[clap(long, multiple_values = true, value_name = "FILES")] | ||
udl_files: Vec<Utf8PathBuf>, | ||
|
||
#[clap(long, multiple_values = true, value_name = "FILES")] | ||
fixture_udl_files: Vec<Utf8PathBuf>, | ||
} | ||
|
||
fn render(out_path: Utf8PathBuf, template: impl Template) -> Result<()> { | ||
println!("rendering {}", out_path); | ||
let contents = template.render()?; | ||
let mut f = | ||
File::create(&out_path).context(format!("Failed to create {:?}", out_path.file_name()))?; | ||
write!(f, "{}\n", contents).context(format!("Failed to write to {}", out_path)) | ||
} | ||
|
||
fn render_cpp( | ||
path: Utf8PathBuf, | ||
prefix: &str, | ||
ci_list: &Vec<ComponentInterface>, | ||
function_ids: &FunctionIds, | ||
object_ids: &ObjectIds, | ||
) -> Result<()> { | ||
render( | ||
path, | ||
CPPScaffoldingTemplate { | ||
prefix, | ||
ci_list, | ||
function_ids: &function_ids, | ||
object_ids: &object_ids, | ||
}, | ||
) | ||
} | ||
|
||
fn render_js( | ||
out_dir: Utf8PathBuf, | ||
ci_list: &Vec<ComponentInterface>, | ||
function_ids: &FunctionIds, | ||
object_ids: &ObjectIds, | ||
) -> Result<()> { | ||
for ci in ci_list { | ||
let path = out_dir.join(format!("{}.jsm", ci.namespace().to_title_case())); | ||
render( | ||
path, | ||
JSBindingsTemplate { | ||
ci, | ||
function_ids: &function_ids, | ||
object_ids: &object_ids, | ||
}, | ||
)?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
pub fn run_main() -> Result<()> { | ||
let args = CliArgs::parse(); | ||
let cis = ComponentInterfaceUniverse::new(args.udl_files, args.fixture_udl_files)?; | ||
let function_ids = FunctionIds::new(&cis); | ||
let object_ids = ObjectIds::new(&cis); | ||
|
||
render_cpp( | ||
args.cpp_path, | ||
"UniFFI", | ||
cis.ci_list(), | ||
&function_ids, | ||
&object_ids, | ||
)?; | ||
render_cpp( | ||
args.fixture_cpp_path, | ||
"UniFFIFixtures", | ||
cis.fixture_ci_list(), | ||
&function_ids, | ||
&object_ids, | ||
)?; | ||
render_js(args.js_dir, cis.ci_list(), &function_ids, &object_ids)?; | ||
render_js( | ||
args.fixture_js_dir, | ||
cis.fixture_ci_list(), | ||
&function_ids, | ||
&object_ids, | ||
)?; | ||
|
||
Ok(()) | ||
} |
Oops, something went wrong.