diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d98fb1fb..ab3856d6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,9 +20,13 @@ jobs: with: distribution: 'corretto' java-version: '17' + - name: Install mdBook + run: curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar -xz - name: Build run: cargo build --verbose - name: Test crates run: cargo test --all-targets --verbose - name: Test client crates run: cargo test --all-targets --verbose --manifest-path=test-crates/Cargo.toml + - name: Test book + run: mdbook test book diff --git a/book/src/call_java_from_rust.md b/book/src/call_java_from_rust.md index ca2361a3..3c7147f6 100644 --- a/book/src/call_java_from_rust.md +++ b/book/src/call_java_from_rust.md @@ -24,7 +24,7 @@ public class Widget { /* ... */ } Using duchess, we can declare a Rust version of this class with the `java_package!` macro: -```rust +```rust,ignore duchess::java_package! { // First, identify the package you are mirroring, // and the visibility level that you want. @@ -44,7 +44,7 @@ duchess::java_package! { This module will expand to a module hierarchy matching the Java package name: -```rust +```rust,ignore pub mod com { pub mod widgard { // One struct per Java class: @@ -80,7 +80,7 @@ pub mod com { Once you've created the Java package, you can create java objects and invoke their methods. This should mostly just work as you would expect, with one twist. Invoking a Java method doesn't immediately cause it to execute. Instead, like an iterator or an async function, it returns a `JvmOp`, which is like a suspended JVM operation that is *ready* to execute. To actually cause the method to execute, you call `execute`. -```rust +```rust,ignore // We need to use `FactoryExt` to call methods on factory: use com::widgard::{Factory, FactoryExt}; @@ -103,7 +103,7 @@ Note that to call methods on the JVM, we first had to start it. You do that via Because jvm-ops are lazy, you can also chain them together: -```rust +```rust,ignore use com::widgard::{Factory, FactoryExt}; let f = Factory::new().execute(); @@ -114,7 +114,7 @@ f.consume_widget(f.produce_widget()).execute(); In fact, using the `inspect` combinator, we can go further: -```rust +```rust,ignore use com::widgard::{Factory, FactoryExt}; duchess::Jvm::with(|jvm| { diff --git a/book/src/derive.md b/book/src/derive.md new file mode 100644 index 00000000..09e2eb65 --- /dev/null +++ b/book/src/derive.md @@ -0,0 +1 @@ +# Deriving Java/Rust conversions diff --git a/book/src/implementing_native_methods.md b/book/src/implementing_native_methods.md index 7a5df85b..3972b30e 100644 --- a/book/src/implementing_native_methods.md +++ b/book/src/implementing_native_methods.md @@ -23,7 +23,7 @@ public class ClassWithNativeMethod { you can provide an implementation for `compute` like so: -```rust +```rust,ignore // First, reflect the class, as described in the "calling Java from Rust" tutorial: duchess::java_package! { package me.ferris; diff --git a/book/src/internals.md b/book/src/internals.md index 75e72c43..72b3992e 100644 --- a/book/src/internals.md +++ b/book/src/internals.md @@ -10,7 +10,7 @@ How the generated code works and why. Java objects are represented by a dummy struct: -```rust +```rust,ignore pub struct MyObject { _dummy: () } @@ -18,7 +18,7 @@ pub struct MyObject { which implements the `JavaObject` trait: -```rust +```rust,ignore unsafe impl JavaObject for MyObject { } ``` @@ -61,7 +61,7 @@ Covers various bits of rationale. We do not want users to have to supply a context object on every method call, so instead we take the lifetime of the returned java reference and tie it to the inputs: -```rust +```rust,ignore // from Java, and ignoring exceptions / null for clarity: // // class MyObject { ReturnType some_method(); } @@ -81,7 +81,7 @@ We have a conflict: * Either we make every method take a jdk pointer context. * Or... we go into a suspended mode... -```rust +```rust,ignore MyObject::new(x, y, z) .execute(jdk); diff --git a/book/src/intro.md b/book/src/intro.md index 7f50fba9..c3f339b3 100644 --- a/book/src/intro.md +++ b/book/src/intro.md @@ -11,7 +11,7 @@ Duchess is a Rust crate that makes it easy, ergonomic, and efficient to interope Duchess permits you to reflect Java classes into Rust and easily invoke methods on Java objects. For example the following Java code... -```rust +```rust,ignore Logger logger = new log.Logger(); logger.addEvent( Event.builder() @@ -23,7 +23,7 @@ logger.addEvent( ...could be executed in Rust as follows: -```rust +```rust,ignore let logger = log::Logger::new().global().execute()?; logger .add_event( diff --git a/book/src/java_package.md b/book/src/java_package.md index 4508e952..786c62ee 100644 --- a/book/src/java_package.md +++ b/book/src/java_package.md @@ -68,7 +68,7 @@ This will generate a Rust module structure containing: For the example above we would get -```rust +```rust,ignore pub mod my { pub mod package { // References to java types branch to duchess; other references @@ -126,7 +126,7 @@ class C2 { } This allows the macro to generate combined Rust modules: -```rust +```rust,ignore pub mod foo { pub mod bar { pub struct C1 { .. } @@ -173,7 +173,7 @@ class C2 { /* you don't have to oxidize any further details */ } When your classes reference other packages that are not currently being oxidixed, duchess will simply generate a reference to those classes. Its your responsibility to bring them into scope. -```rust +```rust,ignore // Bring `q` into scope from somewhere else use some_rust_crate::q; diff --git a/book/src/jvm.md b/book/src/jvm.md index deada42c..3796d656 100644 --- a/book/src/jvm.md +++ b/book/src/jvm.md @@ -17,7 +17,7 @@ Multiple threads can invoke `Jvm::with`, but only one underlying JVM can ever be When you start the JVM from your Rust code, you can set various options by using the jvm builder: -```rust +```rust,ignore Jvm::builder() .add_classpath("foo") .add_classpath("bar") diff --git a/book/src/linking_native_functions.md b/book/src/linking_native_functions.md index 2f61b66e..d458eb63 100644 --- a/book/src/linking_native_functions.md +++ b/book/src/linking_native_functions.md @@ -6,7 +6,7 @@ Using the [`#[java_function]`](./java_function.md) decorator you can write Rust If your Rust program is launching the JVM, then you can configure that JVM to link to your native method definitions through methods on the JVM builder. -```rust +```rust,ignore use duchess::prelude::*; // 👈 You'll need this. #[java_function(...)] @@ -27,7 +27,7 @@ Invoking the link method for every java functon you wish to implement is tedious To avoid this, you can create **suites** of java functions. The idea is that the `link` method accepts both individual `JavaFunction` structs but also `Vec` suites. You can then write a function in your module that returns a `Vec` with all the java functions defined locally: -```rust +```rust,ignore use duchess::prelude::*; #[java_function(...)] @@ -46,7 +46,7 @@ fn java_functions() -> Vec { You can also compose suites from other crates or modules: -```rust +```rust,ignore fn java_functions() -> Vec { crate_a::java_functions() .into_iter() @@ -57,7 +57,7 @@ fn java_functions() -> Vec { And finally you can invoke `link()` to link them all at once: -```rust +```rust,ignore fn main() -> duchess::GlobalResult<()> { Jvm::builder() .link(java_functions()) diff --git a/book/src/methods.md b/book/src/methods.md index ef422c5a..9369ec99 100644 --- a/book/src/methods.md +++ b/book/src/methods.md @@ -2,7 +2,7 @@ When you use duchess, you invoke methods via nice syntax like -```rust +```rust,ignore java_list.get(0).to_string().execute() // ^^^^^^^^^ // This is the method we are discussing here @@ -15,7 +15,7 @@ How does this actually work (and why)? *Part* of our setup is that define relatively ordinary looking inherent methods on the type that defines the method, e.g.: -```rust +```rust,ignore impl Object { fn to_string(&self) -> impl JavaMethod { /* tbd */ } } @@ -118,7 +118,7 @@ In the following sections, we are going to walk through each part of the solutio The first step is to create a "fully qualified" notation for each Java method: -```rust +```rust,ignore impl Object { fn to_string( this: impl IntoJava @@ -139,7 +139,7 @@ where `View` has the same data as `J` but defines inherent methods. We'll create a trait `FromRef` to use for this pattern, where `View: FromRef` indicates that a view `&View` can be constructed from a `&J` reference: -```rust +```rust,ignore pub trait FromRef { fn from_ref(t: &J) -> &Self; } @@ -147,7 +147,7 @@ pub trait FromRef { A view struct is just a newtype on the underlying `J` type but with `#[repr(transparent)]`: -```rust +```rust,ignore #[repr(transparent)] pub struct View { this: J, @@ -158,7 +158,7 @@ The `#[repr(transparent)]` attribute ensures that `J` and `View` have the sam and are treated equivalently in ABIs and the like. Thanks to this, we can implement `FromRef` like so: -```rust +```rust,ignore impl FromRef for View { fn from_ref(t: &J) -> &Self { // Safe because of the `#[repr(transparent)]` attribute @@ -177,7 +177,7 @@ The *method resolution order* for a type `T` is an ordered list of its transitiv For each class `X`, we define a *`ViewAsObj` struct* `ViewAsXObj`: -```rust +```rust,ignore #[repr(transparent)] struct ViewAsXObj { this: J, @@ -194,7 +194,7 @@ The class has two type parameters: Each ViewAsObj struct includes a Deref that derefs to N: -```rust +```rust,ignore impl Deref for ViewAsXObj { type Target = N; @@ -206,7 +206,7 @@ impl Deref for ViewAsXObj { So given `interface Foo extends Bar, Baz`, the type `Foo` would deref to -```rust +```rust,ignore ViewAsFooObj>> // --- ----------- ---------------------------------- // X J N @@ -220,7 +220,7 @@ and so forth. Each op struct implements a trait `FromRef`: -```rust +```rust,ignore trait FromRef { fn from_ref(r: &J) -> &Self; } @@ -230,7 +230,7 @@ The `from_ref` method allows constructing an op struct from an `&J` reference. Implementing this method requires a small bit of unsafe code, leveraging the `repr(transparent)` attribute on each op struct: -```rust +```rust,ignore impl FromRef for ObjectOp where J: IntoJava, @@ -249,7 +249,7 @@ also has inherent methods for each Java method. These are implemented by invoking the [fully qualified inherent functions](). For example, the ViewAsObj struct for `Object` includes a `to_string` method like so: -```rust +```rust,ignore impl ViewAsObjectObj where J: Upcast, @@ -267,7 +267,7 @@ So we create them inside of a `const _: () = { .. }` block. But we do need *some* way to name them. We expose them via associated types of the `JavaView` trait: -```rust +```rust,ignore trait JavaView { type OfObj: FromRef; type OfObjWith: FromRef @@ -279,7 +279,7 @@ trait JavaView { The `OfObj` associated type in particular provides the "default value" for `N` that defines the MRO. The `OfObjWith` is used to supply an explicit `N` value. For example: -```rust +```rust,ignore const _: () = { struct ViewAsFooObj { ... } @@ -299,7 +299,7 @@ The `ViewAsObj` structs allow you to invoke methods on a java object reference l But they do not allow you to invoke methods on some random [`JvmOp`] that happens to *return* a string. For that, we create a very similar set of `ViewAsOp` structs: -```rust +```rust,ignore #[repr(transparent)] struct ViewAsXOp { this: J, @@ -313,7 +313,7 @@ But the signature is slightly different; it is a `&self` method, but the `impl J Instead, it copies the `self.this` out. This relies on the fact that all [`JvmOp`] values are `Copy`. -```rust +```rust,ignore impl ViewAsObjectObj where J: IntoJava, @@ -336,7 +336,7 @@ The reason is that the `ViewAsObjectObj` traits are the output from `Deref` impl We also have to add a `Deref` impl to each of the op structs. -```rust +```rust,ignore struct SomeOp { } impl JvmOp for SomeOp { diff --git a/book/src/to_java.md b/book/src/to_java.md index 5e23be9a..903b0f8c 100644 --- a/book/src/to_java.md +++ b/book/src/to_java.md @@ -19,28 +19,34 @@ e.g., `vec.to_java::>()`. The Rust `String` type converts to the Java string type. One could compute the Java `hashCode` for a string as follows: -```rust +```rust,ignore +use duchess::prelude::*; +use duchess::java; + let data = format!("Hello, Duchess!"); -let hashCode: i32 = - data.to_java() // Returns a `JvmOp` producing a `java::lang::String` - .hashCode() // Returns a `JvmOp` invoking `hashCode` on this string - .execute(); // Execute the jvmop +let hash_code: i32 = + data.to_java::() // Returns a `JvmOp` producing a `java::lang::String` + .hash_code() // Returns a `JvmOp` invoking `hashCode` on this string + .execute()?; // Execute the jvmop ``` ### `Global` Converting a Rust reference to a Java object, such as a `Global` reference, is an identity operation. -```rust +```rust,ignore +use duchess::prelude::*; +use duchess::java; + // Produce a Global reference from a Rust string let data: Global = - format!("Hello, Duchess!").global().execute(); + format!("Hello, Duchess!").global().execute()?; // Invoke `to_java` on the `Global` reference let hashCode: i32 = - data.to_java() // Returns a `JvmOp` producing a `java::lang::String` + data.to_java::() // Returns a `JvmOp` producing a `java::lang::String` .hashCode() // Returns a `JvmOp` invoking `hashCode` on this string - .execute(); // Execute the jvmop + .execute()?; // Execute the jvmop ``` ## Deriving `ToJava` for your own types