Skip to content

Commit

Permalink
Add more README, and a walkthrough of the (barely) working example.
Browse files Browse the repository at this point in the history
  • Loading branch information
rfk committed Jun 10, 2020
1 parent 9586a37 commit 46456a4
Show file tree
Hide file tree
Showing 9 changed files with 975 additions and 60 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Cargo.lock
target
.cargo
.*.swp
*.jar
373 changes: 373 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

234 changes: 178 additions & 56 deletions README.md

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions examples/arithmetic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Example uniffi component: "Arithmetic"

This is a minimal (and very work-in-progress!) example of how to write a Rust component using
uniffi. It doesn't exercise any tricky bits of the FFI so it's a nice place to start. We have
the following so far:

* [`./arithmetic.idl`](./arithmetic.idl), the component interface definition which exposes two
plain functions "add" and "sub". This is processed by functions in [`./build.rs`](./build.rs)
to generate Rust scaffolding for the component, and some Kotlin bindings.
* [`./src/lib.rs`](./src/lib.rs), the core implementation of the component in Rust. This basically
pulls in the generated Rust scaffolding via `include!()` and fills in function implementations.
* A tiny example program in [`./main.kt`](./main.kt) that imports the component in Kotlin, calls
one of its methods and prints the result.
* Some extremely hacky code in [`./build.rs`](./build.rs) that only works on my machine (since it
has some hard-coded file paths) that generates Kotlin bindings from the IDL, compiles them together
with `./main.kt`, and produces a runnabe `.jar` file to exercise the component.

There is a *lot* of build and packaging detail to figure out here, but I'm able to do the following
and actually use the Rust component from Kotlin:

* Install the kotlin command-line compiler.
* Edit `build.rs` to point it to a local copy of the JNA jar.
* Run `cargo build` in this directory; observe that it creates a file `./arithmetic.jar`.
* Try to run `./arithmetic.jar` directly using `java -jar arithmetic.jar`; observe that it fails because it can't find JNA in the classpath, and I can't figure out the right command-line flags to get it to do so.
* Unpack the jar to try running it by hand:
* `mkdir unpacked; cd unpacked`
* `unzip ../arithmetic.jar`
* `unzip -o /path/to/jna-5.2.0.jar`
* `cp ../target/debug/libuniffi_example_arithmetic.dylib ./`
* `java MainKt`
* Observe that it correctly prints the result of some simple arithmetic!

That obviously needs to be smoother, but you get the idea :-)
7 changes: 4 additions & 3 deletions examples/arithmetic/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ fn main() {
}

fn compile_kotlin_example() {
println!("cargo:rerun-if-changed=main.kt");
let mut gen_file = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
gen_file.push("arithmetic.kt");
// There's a whole lot of packaging and dependency-management stuff to figure out here.
// For now I just want to hack it into compiling and running some generated kotlin code.
let status = std::process::Command::new("kotlinc")
.arg("-include-runtime")
.arg("-classpath").arg("/Users/rfk/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna/5.2.0/ed8b772eb077a9cb50e44e90899c66a9a6c00e67/jna-5.2.0.jar")
.arg("/Users/rfk/repos/mozilla/application-services/components/support/android/src/main/java/mozilla/appservices/support/native/Helpers.kt")
.arg("../../src/Helpers.kt")
.arg(gen_file)
.arg("/Users/rfk/repos/mozilla/application-services/main.kt")
.arg("-d").arg("/Users/rfk/repos/mozilla/application-services/arithmetic.jar")
.arg("main.kt")
.arg("-d").arg("arithmetic.jar")
.spawn()
.unwrap()
.wait()
Expand Down
5 changes: 5 additions & 0 deletions examples/arithmetic/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import uniffi.example.Arithmetic;

fun main(args: Array<String>) {
println("2 + 3 = ${Arithmetic.add(2, 3, 1)}")
}
134 changes: 134 additions & 0 deletions examples/fxa-client/fxa-client.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
enum DeviceCapability {
"SEND_TAB",
};

enum DeviceType {
"DESKTOP",
"MOBILE",
};

enum IncomingDeviceCommandType {
"TAB_RECEIVED",
};

enum AccountEventType {
"INCOMING_DEVICE_COMMAND",
"PROFILE_UPDATED",
"DEVICE_CONNECTED",
"ACCOUNT_AUTH_STATE_CHANGED",
"DEVICE_DISCONNECTED",
"ACCOUNT_DESTROYED",
};

dictionary ProfileInfo {
// XXX TODO: probably some of these should be `required`?
string uid;
string email;
string display_name;
string avatar;
boolean avatar_default;
};

dictionary AccessTokenInfo {
required string scope;
required string token;
ScopedKey key;
required uint64 expires_at;
};

dictionary ScopedKey {
required string kty;
required string scope;
required string k;
required string kid;
};

dictionary IntrospectInfo {
required boolean active = false;
};

dictionary DeviceInfo {
required string id;
required string display_name;
required DeviceType type;
required boolean is_current_device;
uint64 last_access_time;
PushSubscription push_subscription;
required boolean push_endpoint_expired;
sequence<DeviceCapability> capabilities;
};

dictionary TabHistoryEntry {
required string title;
required string url;
};

dictionary SendTabData {
DeviceInfo from;
sequence<TabHistoryEntry> entries;
};

dictionary IncomingDeviceCommand {
IncomingDeviceCommandType type;
SendTabData data; /* eventually a union type..? */
};

dictionary DeviceConnectedData {
string name;
};

dictionary DeviceDisconnecedData {
required string id;
required boolean is_local_device;
};

dictionary AccountEvent {
AccountEventType type;
// No support for union types yet...
// (IncomingDeviceCommand or DeviceConnectedData or DeviceDisconnectesData) data;
};

dictionary MigrationState {
string blah = "blah";
};

interface FirefoxAccount {
constructor(string content_url, string client_id, string redirect_uri, optional string token_server_url_override);
/*static FirefoxAccont fromJSON(string json); TODO: alternative constructors */
string toJSON();

string getPairingAuthorityURL();
string getTokenServerEndpointURL();
string getConnectionSuccessURL();
string getManageAccountURL();
string getManageDevicesURL();

string beginOAuthFlow(sequence<string> scopes);
string beginPairingFlow(string pairingUrl, sequence<string> scopes);
void completeOAuthFlow(string code, string state);
void disconnect();

IntrospectInfo checkAuthorizationStatus();
AccessTokenInfo getAccessToken(string scope, optional u32 ttl);
string getSessionToken(); // really whish we weren't exposing this... :-(
string getCurrentDeviceId();
string authorizeOAuthCode(string client_id, sequence<string> scopes, string state, optional string access_type = "online");
void clearAccessTokenCache();

string copyFromSessionToken(string sessionToken, string kSync, string kXCS); // needs better return type...
string migrateFromSessionToken(string sessionToken, string kSync, string kXCS); // needs better return type...
string retryMigrateFromSessionToken(); // needs better return type...
MigrationState isInMigrationState();

ProfileInfo getProfile(optional boolean ignoreCache=false);

void initializeDevice(string name, DeviceType type, sequence<DeviceCapability> supportedCapabilities);
void ensureCapabilities(sequence<DeviceCapability> supportedCapabilities);
void setDevicePushSubscription(string endpoint, string publicKey, string authKey);
void setDeviceDisplayName(string display_name);
sequence<DeviceInfo> getDevices(optional boolean ignoreCache = false);
sequence<AccountEvent> handlePushMessage(string payload);

sequence<IncomingDeviceCommand> pollDeviceCommands();
void sendSingleTab(string target_device_id, string title, string url);
};
Loading

0 comments on commit 46456a4

Please sign in to comment.