diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f414866 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +## 0.1.0 + +Initial pre-release. This library will be used to provide Java bindings for [IronCore Alloy](https://github.com/IronCoreLabs/ironcore-alloy/tree/main). It will recieve frequent breaking changes initially as we find improvements through that libaries' usage of it. + diff --git a/Cargo.lock b/Cargo.lock index c94860c..7973fd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,20 +402,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "cc" version = "1.0.99" @@ -1449,10 +1435,11 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "uniffi" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "camino", + "cargo_metadata", "clap", "uniffi_bindgen", "uniffi_build", @@ -1467,7 +1454,7 @@ dependencies = [ "anyhow", "askama", "camino", - "cargo_metadata 0.18.1", + "cargo_metadata", "clap", "glob", "heck", @@ -1495,7 +1482,7 @@ dependencies = [ [[package]] name = "uniffi-example-arithmetic" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "thiserror", "uniffi", @@ -1504,7 +1491,7 @@ dependencies = [ [[package]] name = "uniffi-example-custom-types" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "bytes", @@ -1515,7 +1502,7 @@ dependencies = [ [[package]] name = "uniffi-example-futures" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "async-std", "thiserror", @@ -1525,7 +1512,7 @@ dependencies = [ [[package]] name = "uniffi-example-geometry" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "uniffi", ] @@ -1533,7 +1520,7 @@ dependencies = [ [[package]] name = "uniffi-example-rondpoint" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "uniffi", ] @@ -1541,7 +1528,7 @@ dependencies = [ [[package]] name = "uniffi-fixture-coverall" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "once_cell", "thiserror", @@ -1551,7 +1538,7 @@ dependencies = [ [[package]] name = "uniffi-fixture-ext-types" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "bytes", @@ -1567,7 +1554,7 @@ dependencies = [ [[package]] name = "uniffi-fixture-ext-types-custom-types" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "bytes", @@ -1578,12 +1565,12 @@ dependencies = [ [[package]] name = "uniffi-fixture-ext-types-external-crate" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" [[package]] name = "uniffi-fixture-ext-types-lib-one" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "bytes", @@ -1593,7 +1580,7 @@ dependencies = [ [[package]] name = "uniffi-fixture-ext-types-sub-lib" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "uniffi", @@ -1603,7 +1590,7 @@ dependencies = [ [[package]] name = "uniffi-fixture-futures" version = "0.21.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "async-trait", "futures", @@ -1616,7 +1603,7 @@ dependencies = [ [[package]] name = "uniffi-fixture-time" version = "0.22.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "chrono", "thiserror", @@ -1626,12 +1613,12 @@ dependencies = [ [[package]] name = "uniffi_bindgen" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "askama", "camino", - "cargo_metadata 0.15.4", + "cargo_metadata", "fs-err", "glob", "goblin", @@ -1642,14 +1629,13 @@ dependencies = [ "textwrap", "toml", "uniffi_meta", - "uniffi_testing", "uniffi_udl", ] [[package]] name = "uniffi_build" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "camino", @@ -1659,7 +1645,7 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "quote", "syn", @@ -1668,7 +1654,7 @@ dependencies = [ [[package]] name = "uniffi_core" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "async-compat", @@ -1683,7 +1669,7 @@ dependencies = [ [[package]] name = "uniffi_macros" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "bincode", "camino", @@ -1700,7 +1686,7 @@ dependencies = [ [[package]] name = "uniffi_meta" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "bytes", @@ -1711,11 +1697,11 @@ dependencies = [ [[package]] name = "uniffi_testing" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "camino", - "cargo_metadata 0.15.4", + "cargo_metadata", "fs-err", "once_cell", ] @@ -1723,7 +1709,7 @@ dependencies = [ [[package]] name = "uniffi_udl" version = "0.28.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "anyhow", "textwrap", @@ -1858,7 +1844,7 @@ dependencies = [ [[package]] name = "weedle2" version = "5.0.0" -source = "git+https://github.com/skeet70/uniffi-rs.git?branch=thread-external-type-metadata-rustbuffer#fecb169c8bafae5c16895773edea11061c4ba37a" +source = "git+https://github.com/mozilla/uniffi-rs.git?branch=main#106f0b2d8a4680fd47585de0e9986c1261992926" dependencies = [ "nom", ] diff --git a/Cargo.toml b/Cargo.toml index 61cd918..8fa962d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,10 @@ path = "src/main.rs" anyhow = "1" askama = { version = "0.12", default-features = false, features = ["config"] } camino = "1.1.6" +cargo_metadata = "0.15" clap = { version = "4", default-features = false, features = [ "derive", + "help", "std", "cargo", ] } @@ -27,20 +29,19 @@ regex = "1.10.4" serde = "1" textwrap = "0.16.1" toml = "0.5" # can't be on 8, `Value` is part of public interface -uniffi_bindgen = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi_meta = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi_meta = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } [dev-dependencies] -cargo_metadata = "0.18.1" glob = "0.3" itertools = "0.13.0" -uniffi-example-arithmetic = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-example-custom-types = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-example-futures = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-example-geometry = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-example-rondpoint = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-fixture-coverall = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-fixture-ext-types = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-fixture-futures = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi-fixture-time = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } -uniffi_testing = { git = "https://github.com/skeet70/uniffi-rs.git", branch = "thread-external-type-metadata-rustbuffer" } +uniffi-example-arithmetic = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-example-custom-types = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-example-futures = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-example-geometry = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-example-rondpoint = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-fixture-coverall = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-fixture-ext-types = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-fixture-futures = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi-fixture-time = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } +uniffi_testing = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "main" } diff --git a/README.md b/README.md index d52a8f8..162e7d6 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,181 @@ # uniffi-bindgen-java +Generate [UniFFI](https://github.com/mozilla/uniffi-rs) bindings for Java. + +Official Kotlin bindings already exist, which can be used by any JVM language including Java. The Java specific bindings use Java-native types where possible for a more ergonomic interface, for example the Java bindings use `CompletableFutures` instead of `kotlinx.coroutines`. + +We highly reccommend you use [UniFFI's proc-macro definition](https://mozilla.github.io/uniffi-rs/latest/proc_macro/index.html) instead of UDL where possible. + ## Requirements * Java 20+: `javac`, and `jar` * The [Java Native Access](https://github.com/java-native-access/jna#download) JAR downloaded and its path added to your `$CLASSPATH` environment variable. +## Installation + +MSRV is `1.77.0`. + +`cargo install uniffi-bindgen-java --git https://github.com/IronCoreLabs/uniffi-bindgen-java` + +## Usage + +``` +uniffi-bindgen-java --help +Java scaffolding and bindings generator for Rust + +Usage: + +Commands: + generate Generate Java bindings + scaffolding Generate Rust scaffolding code + print-repr Print a debug representation of the interface from a dynamic library + +Options: + -h, --help Print help + -V, --version Print version +``` + +### Generate Bindings + +``` +uniffi-bindgen-java generate --help +Generate Java bindings + +Usage: + +Arguments: + Path to the UDL file, or cdylib if `library-mode` is specified + +Options: + -o, --out-dir Directory in which to write generated files. Default is same folder as .udl file + -n, --no-format Do not try to format the generated bindings + -c, --config Path to optional uniffi config file. This config is merged with the `uniffi.toml` config present in each crate, with its values taking precedence + --lib-file Extract proc-macro metadata from a native lib (cdylib or staticlib) for this crate + --library Pass in a cdylib path rather than a UDL file + --crate When `--library` is passed, only generate bindings for one crate. When `--library` is not passed, use this as the crate name instead of attempting to locate and parse Cargo.toml +``` + +As an example: + +``` +> git clone https://github.com/mozilla/uniffi-rs.git +> cd uniffi-rs/examples/arithmetic-proc-macro +> cargo b --release +> uniffi-bindgen-java generate --out-dir ./generated-java --library ../../target/release/libarithmeticpm.so +> ll generated-java/uniffi/arithmeticpm/ +total 216 +-rw-r--r-- 1 user users 295 Jul 24 13:02 ArithmeticExceptionErrorHandler.java +-rw-r--r-- 1 user users 731 Jul 24 13:02 ArithmeticException.java +-rw-r--r-- 1 user users 3126 Jul 24 13:02 Arithmeticpm.java +-rw-r--r-- 1 user users 512 Jul 24 13:02 AutoCloseableHelper.java +-rw-r--r-- 1 user users 584 Jul 24 13:02 FfiConverterBoolean.java +... +> cat generated-java/uniffi/arithmeticpm/Arithmeticpm.java +package uniffi.arithmeticpm; + + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +public class Arithmeticpm { + public static Long add(Long a, Long b) throws ArithmeticException { + try { +... + +``` + +### Generate Scaffolding + +``` +uniffi-bindgen-java scaffolding --help +Generate Rust scaffolding code + +Usage: + +Arguments: + Path to the UDL file + +Options: + -o, --out-dir Directory in which to write generated files. Default is same folder as .udl file + -n, --no-format Do not try to format the generated bindings +``` + +### Print Debug Representation + +``` +uniffi-bindgen-java print-repr --help +Print a debug representation of the interface from a dynamic library + +Usage: + +Arguments: + Path to the library file (.so, .dll, .dylib, or .a) +``` + +## Integrating Bindings + +After generation you'll have an `--out-dir` full of Java files. Package those into a `.jar` using your build tools of choice, and the result can be imported and used as per normal in any Java project with the `JNA` dependency available. + +Any top level functions in the Rust library will be static methods in a class named after the crate. + +## Configuration + +The generated Java can be configured using a `uniffi.toml` configuration file. + +| Configuration name | Default | Description | +| --- | --- | --- | +| `package_name` | `uniffi` | The Java package name - ie, the value use in the `package` statement at the top of generated files. | +| `cdylib_name` | `uniffi_{namespace}` | The name of the compiled Rust library containing the FFI implementation (not needed when using `generate --library`) | +| `generate_immutable_records` | `false` | Whether to generate records with immutable fields (`record` instead of `class`). | +| `custom_types` | | A map which controls how custom types are exposed to Java. See the [custom types section of the UniFFI manual](https://mozilla.github.io/uniffi-rs/latest/udl/custom_types.html#custom-types-in-the-bindings-code) | +| `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Java package which will be used referring to types in that crate. See the [external types section of the manual](https://mozilla.github.io/uniffi-rs/latest/udl/ext_types_external.html#kotlin) | +| `android` | `false` | Used to toggle on Android specific optimizations (warning: not well tested yet) | +| `android_cleaner` | `android` | Use the `android.system.SystemCleaner` instead of `java.lang.ref.Cleaner`. Fallback in both instances is the one shipped with JNA. | + +### Example + +#### Custom types + +``` +[bindings.java] +package_name = "customtypes" + +[bindings.java.custom_types.Url] +# Name of the type in the Java code +type_name = "URL" +# Classes that need to be imported +imports = ["java.net.URI", "java.net.URL"] +# Functions to convert between strings and URLs +into_custom = "new URI({}).toURL()" +from_custom = "{}.toString()" +``` + +#### External Types + +``` +[bindings.java.external_packages] +# This specifies that external types from the crate `rust-crate-name` will be referred by by the package `"java.package.name`. +rust-crate-name = "java.package.name" +``` + ## Notes - failures in CompletableFutures will cause them to `completeExceptionally`. The error that caused the failure can be checked with `e.getCause()`. When implementing an async Rust trait in Java, you'll need to `completeExceptionally` instead of throwing. See `TestFixtureFutures.java` for an example trait implementation with errors. +- all primitives are signed in Java by default. Rust correctly interprets the a signed primitive value from Java as unsigned when told to. Callers of Uniffi functions need to be aware when making comparisons (`compareUnsigned`) or printing when a value is actually unsigned to code around footguns on this side. ## Unsupported features * Defaults aren't supported in Java so [uniffi struct, method, and function defaults](https://mozilla.github.io/uniffi-rs/proc_macro/index.html#default-values) don't exist in the Java code. *Note*: a reasonable case could be made for supporting defaults on structs by way of generated builder patterns. PRs welcome. +* Output formatting isn't currently supported because a standalone command line Java formatter wasn't found. PRs welcome enabling that feature, the infrastructure is in place. ## Testing -We pull down the pinned examples directly from Uniffi and run Java tests using the generated bindings. Just run `cargo t` to run all of them. +We pull down the pinned examples directly from Uniffi and run Java tests using the generated bindings. Run `cargo t` to run all of them. + +Note that if you need additional toml entries for your test, you can put a `uniffi-extras.toml` as a sibling of the test and it will be read in addition to the base `uniffi.toml` for the example. See [CustomTypes](./tests/scripts/TestCustomTypes/) for an example. Settings in `uniffi-extras.toml` apply across all namespaces. -Note that if you need additional toml entries for your test, you can put a `uniffi-extras.toml` as a sibling of the test and it will be read in addition to the base `uniffi.toml` for the example. See [CustomTypes](./tests/scripts/TestCustomTypes/) for an example. +## Versioning -## TODO +`uniffi-bindgen-java` is versioned separately from `uniffi-rs`. We follow the [Cargo SemVer rules](https://doc.rust-lang.org/cargo/reference/resolver.html#semver-compatibility), so versions are compatible if their left-most non-zero major/minor/patch component is the same. Any modification to the generator that causes either a consumer of the generated code to need to make changes is considered breaking. -- optimize when primitive and boxed types are used. Boxed types are needed when referencing builtins as generics, but we could be using primitives in a lot more function arguments, return types, and value definitions. -- methods that return `Result` in Rust should probably `T blah() throws SpecificException` in Java. As is, there are a lot of hard to handle `RuntimeException`s and the same thing needs to be done when someone implements a trait in Java (see `TestFixtureFutures.java`). -- our use case almost certainly requires older Java versions than 20/21. Investigate supporting back to Java 8, which seems to be the common library target. +`uniffi-bindgen-java` is currently unstable and being developed by IronCore Labs to target features required by [`ironcore-alloy`](https://github.com/IronCoreLabs/ironcore-alloy/). The major version is currently 0, and most changes are likely to bump the minor version. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..a173441 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1 @@ +To release a new version create a `CHANGELOG.md` entry, bump the `Cargo.toml` version, create a matching tag, and push all of the above. diff --git a/src/gen_java/mod.rs b/src/gen_java/mod.rs index 4f888a5..59e4a92 100644 --- a/src/gen_java/mod.rs +++ b/src/gen_java/mod.rs @@ -405,9 +405,10 @@ impl JavaCodeOracle { fn ffi_type_label(&self, ffi_type: &FfiType, config: &Config) -> String { match ffi_type { - // Note that unsigned integers in Java are currently experimental, but java.nio.ByteBuffer does not - // support them yet. Thus, we use the signed variants to represent both signed and unsigned - // types from the component API. + // Note that unsigned values in Java don't have true native support. Signed primitives + // can contain unsigned values and there are methods like `Integer.compareUnsigned` + // that respect the unsigned value, but knowledge outside the type system is required. + // TODO(java): improve callers knowledge of what contains an unsigned value FfiType::Int8 | FfiType::UInt8 => "Byte".to_string(), FfiType::Int16 | FfiType::UInt16 => "Short".to_string(), FfiType::Int32 | FfiType::UInt32 => "Integer".to_string(), @@ -817,23 +818,32 @@ mod filters { pub fn async_complete( callable: impl Callable, ci: &ComponentInterface, + config: &Config, ) -> Result { let ffi_func = callable.ffi_rust_future_complete(ci); let call = format!("UniffiLib.INSTANCE.{ffi_func}(future, continuation)"); let call = match callable.return_type() { Some(Type::External { kind: ExternalKind::DataClass, - name, + module_path, + namespace, .. }) => { // Need to convert the RustBuffer from our package to the RustBuffer of the external package - let suffix = JavaCodeOracle.class_name(ci, &name); - // TODO(murph): make this Java - format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}") + let rust_buffer = format!( + "{}.RustBuffer", + config.external_type_package_name(&module_path, &namespace) + ); + format!( + "(future, continuation) -> {{ + var result = {call}; + return {rust_buffer}.create(result.capacity, result.len, result.data); + }}" + ) } - _ => call, + _ => format!("(future, continuation) -> {call}"), }; - Ok(format!("(future, continuation) -> {call}")) + Ok(call) } pub fn async_free( diff --git a/src/lib.rs b/src/lib.rs index 1c54758..119018f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,13 +100,11 @@ impl BindingGenerator for JavaBindingGenerator { } } -// TODO(murph): --help and -h still aren't working -/// Scaffolding and bindings generator for Rust #[derive(Parser)] #[clap(name = "uniffi-bindgen-java")] #[clap(version = clap::crate_version!())] -#[clap(propagate_version = true)] -#[command(version, about, long_about = None)] +#[clap(propagate_version = true, disable_help_subcommand = true)] +/// Java scaffolding and bindings generator for Rust struct Cli { #[clap(subcommand)] command: Commands, @@ -144,6 +142,14 @@ enum Commands { /// Path to the UDL file, or cdylib if `library-mode` is specified source: Utf8PathBuf, + + /// Whether we should exclude dependencies when running "cargo metadata". + /// This will mean external types may not be resolved if they are implemented in crates + /// outside of this workspace. + /// This can be used in environments when all types are in the namespace and fetching + /// all sub-dependencies causes obscure platform specific problems. + #[clap(long)] + metadata_no_deps: bool, }, /// Generate Rust scaffolding code Scaffolding { @@ -176,6 +182,7 @@ pub fn run_main() -> Result<()> { library_mode, crate_name, source, + metadata_no_deps, } => { if library_mode { use uniffi_bindgen::library_mode::generate_bindings; @@ -183,10 +190,22 @@ pub fn run_main() -> Result<()> { panic!("--lib-file is not compatible with --library.") } let out_dir = out_dir.expect("--out-dir is required when using --library"); + + let config_supplier = { + use uniffi_bindgen::cargo_metadata::CrateConfigSupplier; + let mut cmd = cargo_metadata::MetadataCommand::new(); + if metadata_no_deps { + cmd.no_deps(); + } + let metadata = cmd.exec()?; + CrateConfigSupplier::from(metadata) + }; + generate_bindings( &source, crate_name, &JavaBindingGenerator, + &config_supplier, config.as_deref(), &out_dir, !no_format, diff --git a/src/templates/Async.java b/src/templates/Async.java index e51ef16..20619f1 100644 --- a/src/templates/Async.java +++ b/src/templates/Async.java @@ -98,9 +98,6 @@ static CompletableFuture uniffiRustCallAsync( ){ CompletableFuture future = new UniffiFreeingFuture<>(rustFuture, freeFunc); - // TODO(murph): may want an overload that takes an executor to run on. - // That may be misleading though, since the actual work is running in Rust's - // async runtime, not the provided executor. CompletableFuture.runAsync(() -> { try { byte pollResult; @@ -135,7 +132,10 @@ private static byte poll(long rustFuture, PollingFunction pollFunc) throws Inter var handle = uniffiContinuationHandleMap.insert(pollFuture); pollFunc.apply(rustFuture, UniffiRustFutureContinuationCallbackImpl.INSTANCE, handle); - // block until the poll completes + // busy-wait until the poll completes + // TODO(java): may be more efficient to use a CountdownLatch here instead of a CF we end up busy-waiting + // because of Java bugs. + do {} while (!pollFuture.isDone()); return pollFuture.get(); } @@ -168,7 +168,7 @@ static UniffiForeignFuture uniffiTraitInterfaceCallAsync( }); long handle = uniffiForeignFutureHandleMap.insert(job); - return new UniffiForeignFuture(handle, new UniffiForeignFutureFreeImpl(foreignFutureCf)); + return new UniffiForeignFuture(handle, new UniffiForeignFutureFreeImpl(foreignFutureCf)); } @SuppressWarnings("unchecked") @@ -209,7 +209,7 @@ static UniffiForeignFuture uniffiTraitInterfaceCallAsyn }); long handle = uniffiForeignFutureHandleMap.insert(job); - return new UniffiForeignFuture(handle, new UniffiForeignFutureFreeImpl(foreignFutureCf)); + return new UniffiForeignFuture(handle, new UniffiForeignFutureFreeImpl(foreignFutureCf)); } static class UniffiForeignFutureFreeImpl implements UniffiForeignFutureFree { diff --git a/src/templates/MapTemplate.java b/src/templates/MapTemplate.java index 363f796..c6652f2 100644 --- a/src/templates/MapTemplate.java +++ b/src/templates/MapTemplate.java @@ -4,6 +4,7 @@ import java.nio.ByteBuffer; import java.util.Map; +import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.Collectors; diff --git a/src/templates/macros.java b/src/templates/macros.java index 03ff92a..d5df462 100644 --- a/src/templates/macros.java +++ b/src/templates/macros.java @@ -87,7 +87,7 @@ UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}), {%- endif %} {{ callable|async_poll(ci) }}, - {{ callable|async_complete(ci) }}, + {{ callable|async_complete(ci, config) }}, {{ callable|async_free(ci) }}, // lift function {%- match callable.return_type() %} diff --git a/tests/scripts/TestCustomTypes/uniffi-extras.toml b/tests/scripts/TestCustomTypes/uniffi-extras.toml index 35488be..e3ccf0f 100644 --- a/tests/scripts/TestCustomTypes/uniffi-extras.toml +++ b/tests/scripts/TestCustomTypes/uniffi-extras.toml @@ -2,7 +2,7 @@ package_name = "customtypes" [bindings.java.custom_types.Url] -# Name of the type in the Kotlin code +# Name of the type in the Java code type_name = "URL" # Classes that need to be imported imports = ["java.net.URI", "java.net.URL"] diff --git a/tests/scripts/TestFixtureFutures/TestFixtureFutures.java b/tests/scripts/TestFixtureFutures/TestFixtureFutures.java index 14d84e6..09320c0 100644 --- a/tests/scripts/TestFixtureFutures/TestFixtureFutures.java +++ b/tests/scripts/TestFixtureFutures/TestFixtureFutures.java @@ -22,6 +22,7 @@ public interface FutureRunnable { void run() throws InterruptedException, ExecutionException; } + static long nano_to_millis = 1_000_000; public static long measureTimeMillis(FutureRunnable r) { long startTimeNanos = System.nanoTime(); try { @@ -30,14 +31,14 @@ public static long measureTimeMillis(FutureRunnable r) { assert false : "unexpected future run failure"; } long endTimeNanos = System.nanoTime(); - long elapsedTimeMillis = (endTimeNanos - startTimeNanos) / 1_000_000; + long elapsedTimeMillis = (endTimeNanos - startTimeNanos) / nano_to_millis; return elapsedTimeMillis; } public static void assertReturnsImmediately(long actualTime, String testName) { - // TODO(murph): 4ms limit in Kotlin - assert actualTime <= 15 : MessageFormat.format("unexpected {0} time: {1}ms", testName, actualTime); + // TODO(java): 4ms limit in Kotlin + assert actualTime <= 20 : MessageFormat.format("unexpected {0} time: {1}ms", testName, actualTime); } public static void assertApproximateTime(long actualTime, int expectedTime, String testName) { @@ -263,14 +264,14 @@ public CompletableFuture tryDelay(String delayMs) { } } var completedDelaysBefore = traitObj.completedDelays; - Futures.cancelDelayUsingTrait(traitObj, 10).get(); + Futures.cancelDelayUsingTrait(traitObj, 50).get(); // sleep long enough so that the `delay()` call would finish if it wasn't cancelled. - TestFixtureFutures.delay(100).get(); + TestFixtureFutures.delay(200).get(); // If the task was cancelled, then completedDelays won't have increased assert traitObj.completedDelays == completedDelaysBefore : MessageFormat.format("{0} current delays != {1} delays before", traitObj.completedDelays, completedDelaysBefore); // Test that all handles were cleaned up - // TODO(murph): this is inconsistently failing in CI, touch + System.gc(); var endingHandleCount = UniffiAsyncHelpers.uniffiForeignFutureHandleCount(); assert endingHandleCount == 0 : MessageFormat.format("{0} current handle count != 0", endingHandleCount); } diff --git a/tests/scripts/TestImportedTypes.java b/tests/scripts/TestImportedTypes/TestImportedTypes.java similarity index 92% rename from tests/scripts/TestImportedTypes.java rename to tests/scripts/TestImportedTypes/TestImportedTypes.java index da962fb..a4a0b47 100644 --- a/tests/scripts/TestImportedTypes.java +++ b/tests/scripts/TestImportedTypes/TestImportedTypes.java @@ -15,7 +15,7 @@ public static void main(String[] args) throws Exception { var ct = ImportedTypesLib.getCombinedType(null); assert ct.uot().sval().equals("hello"); assert ct.guid().equals(new Guid("a-guid")); - assert ct.url().equals(new Url("http://example.com/")); + assert ct.url().equals(new Url(new java.net.URL("http://example.com/"))); var ct2 = ImportedTypesLib.getCombinedType(null); assert ct.equals(ct2); @@ -27,9 +27,7 @@ public static void main(String[] args) throws Exception { assert ImportedTypesSublib.getSubType(null).maybeInterface() == null; assert ImportedTypesSublib.getTraitImpl().hello().equals("sub-lib trait impl says hello"); - // TODO(uniffi): support adding uniffi.toml values to dependency crates - // var url = new java.net.URL("http://example.com/"); - var url = new Url("http://example.com/"); + var url = new Url(new java.net.URL("http://example.com/")); assert ImportedTypesLib.getUrl(url).equals(url); assert ImportedTypesLib.getMaybeUrl(url).equals(url); assert ImportedTypesLib.getMaybeUrl(null) == null; diff --git a/tests/scripts/TestImportedTypes/uniffi-extras.toml b/tests/scripts/TestImportedTypes/uniffi-extras.toml new file mode 100644 index 0000000..931c507 --- /dev/null +++ b/tests/scripts/TestImportedTypes/uniffi-extras.toml @@ -0,0 +1,8 @@ +[bindings.java.custom_types.Url] +# Name of the type in the Java code +type_name = "URL" +# Classes that need to be imported +imports = ["java.net.URI", "java.net.URL"] +# Functions to convert between strings and URLs +into_custom = "new URI({}).toURL()" +from_custom = "{}.toString()" diff --git a/tests/scripts/TestRondpoint.java b/tests/scripts/TestRondpoint.java index ea285e5..dfbe8fa 100644 --- a/tests/scripts/TestRondpoint.java +++ b/tests/scripts/TestRondpoint.java @@ -99,7 +99,6 @@ public static void main(String[] args) throws Exception { // Booleans affirmEnchaine(List.of(true, false), st::toStringBoolean, TestRondpoint::defaultStringyEquals); - // TODO(murph): make this more clear to Java callers on methods // All primitives are signed in Java by default. Rust correctly interprets the same signed max as an unsigned max // when told to. We have to mask the value we expect on the comparison side for Java, or else it will toString them // as signed values. Callers of Uniffi functions need to be aware when making comparisons (`compareUnsigned`) or diff --git a/tests/tests.rs b/tests/tests.rs index 92c4663..8e00217 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -57,11 +57,19 @@ fn run_test(fixture_name: &str, test_file: &str) -> Result<()> { } }; + let config_supplier = { + use uniffi_bindgen::cargo_metadata::CrateConfigSupplier; + let cmd = cargo_metadata::MetadataCommand::new(); + let metadata = cmd.exec()?; + CrateConfigSupplier::from(metadata) + }; + // generate the fixture bindings generate_bindings( &cdylib_path, None, &JavaBindingGenerator, + &config_supplier, maybe_new_uniffi_toml_filename.as_deref(), &out_dir, true, @@ -253,7 +261,7 @@ fixture_tests! { (test_chronological, "uniffi-fixture-time", "scripts/TestChronological.java"), (test_custom_types, "uniffi-example-custom-types", "scripts/TestCustomTypes/TestCustomTypes.java"), // (test_callbacks, "uniffi-fixture-callbacks", "scripts/test_callbacks.java"), - (test_external_types, "uniffi-fixture-ext-types", "scripts/TestImportedTypes.java"), + (test_external_types, "uniffi-fixture-ext-types", "scripts/TestImportedTypes/TestImportedTypes.java"), (test_futures, "uniffi-example-futures", "scripts/TestFutures.java"), (test_futures_fixtures, "uniffi-fixture-futures", "scripts/TestFixtureFutures/TestFixtureFutures.java"), }