Skip to content

Commit 7e5b024

Browse files
committed
move user guide into docc
1 parent cf34a47 commit 7e5b024

File tree

3 files changed

+254
-262
lines changed

3 files changed

+254
-262
lines changed

Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md

Lines changed: 251 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,255 @@
22

33
The `swift-java` command line tool offers multiple ways to interact your Java interoperability enabled projects.
44

5-
## Generating Swift bindings to Java libraries
5+
## swift-java
66

7-
The `swift-java wrap-java` command.
7+
The `swift-java` command line tool offers multiple modes which you can use to prepare your Swift and Java code to interact with eachother.
8+
9+
The following sections will explain the modes in depth. When in doubt, you can always use the command line `--help` to get additional
10+
guidance about the tool and available options:
11+
12+
```bash
13+
> swift-java --help
14+
15+
USAGE: swift-java <subcommand>
16+
17+
OPTIONS:
18+
-h, --help Show help information.
19+
20+
SUBCOMMANDS:
21+
configure Configure and emit a swift-java.config file based on an input dependency or jar file
22+
resolve Resolve dependencies and write the resulting swift-java.classpath file
23+
wrap-java Wrap Java classes with corresponding Swift bindings.
24+
jextract Wrap Swift functions and types with Java bindings, making them available to be called from Java
25+
26+
See 'swift-java help <subcommand>' for detailed help.
27+
```
28+
29+
## Expose Java classes to Swift: swift-java wrap-java
30+
31+
The `swift-java` is a Swift program that uses Java's runtime reflection facilities to translate the requested Java classes into their Swift projections. The output is a number of Swift source files, each of which corresponds to a
32+
single Java class. The `swift-java` can be executed like this:
33+
34+
```
35+
swift-java help wrap-java
36+
```
37+
38+
to produce help output like the following:
39+
40+
```
41+
USAGE: swift-java wrap-java [--output-directory <output-directory>] [--input-swift <input-swift>] [--log-level <log-level>] [--cp <cp> ...] [--filter-java-package <filter-java-package>] --swift-module <swift-module> [--depends-on <depends-on> ...] [--swift-native-implementation <swift-native-implementation> ...] [--cache-directory <cache-directory>] [--swift-match-package-directory-structure <swift-match-package-directory-structure>] <input>
42+
43+
ARGUMENTS:
44+
<input> Path to .jar file whose Java classes should be wrapped using Swift bindings
45+
46+
OPTIONS:
47+
-o, --output-directory <output-directory>
48+
The directory in which to output generated SwiftJava configuration files.
49+
--input-swift <input-swift>
50+
Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.
51+
-l, --log-level <log-level>
52+
Configure the level of logs that should be printed (values: trace, debug, info, notice, warning, error, critical; default: log level)
53+
--cp, --classpath <cp> Class search path of directories and zip/jar files from which Java classes can be loaded.
54+
-f, --filter-java-package <filter-java-package>
55+
While scanning a classpath, inspect only types included in this package
56+
--swift-module <swift-module>
57+
The name of the Swift module into which the resulting Swift types will be generated.
58+
--depends-on <depends-on>
59+
A swift-java configuration file for a given Swift module name on which this module depends,
60+
e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options
61+
for each Swift module that this module depends on (transitively) that contains wrapped Java sources.
62+
--swift-native-implementation <swift-native-implementation>
63+
The names of Java classes whose declared native methods will be implemented in Swift.
64+
--cache-directory <cache-directory>
65+
Cache directory for intermediate results and other outputs between runs
66+
--swift-match-package-directory-structure <swift-match-package-directory-structure>
67+
Match java package directory structure with generated Swift files (default: false)
68+
-h, --help Show help information.
69+
70+
```
71+
72+
For example, the `JavaKitJar` library is generated with this command line:
73+
74+
```swift
75+
swift-java wrap-java --swift-module JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config
76+
```
77+
78+
The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated.
79+
80+
The `--depends-on` option is followed by the swift-java configuration files for any library on which this Swift library depends. Each `--depends-on` option is of the form `<swift library name>=<swift-java.config path>`, and tells swift-java which other Java classes have already been translated to Swift. For example, if your Java class uses `java.net.URL`, then you should include
81+
`JavaKitNetwork`'s configuration file as a dependency here.
82+
83+
The `-o` option specifies the output directory. Typically, this will be `Sources/<module name>/generated` or similar to keep the generated Swift files separate from any hand-written ones. To see the output on the terminal rather than writing files to disk, pass `-` for this option.
84+
85+
Finally, the command line should contain the `swift-java.config` file containing the list of classes that should be translated into Swift and their corresponding Swift type names. The tool will output a single `.swift` file for each class, along with warnings for any public API that cannot be translated into Swift. The most common warnings are due to missing Swift projections for Java classes. For example, here we have not translated (or provided the translation manifests for) the Java classes
86+
`java.util.zip.ZipOutputStream` and `java.io.OutputStream`:
87+
88+
```
89+
warning: Unable to translate 'java.util.jar.JarOutputStream' superclass: Java class 'java.util.zip.ZipOutputStream' has not been translated into Swift
90+
warning: Unable to translate 'java.util.jar.JarOutputStream' constructor: Java class 'java.io.OutputStream' has not been translated into Swift
91+
warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo': Java class 'java.io.OutputStream' has not been translated into Swift
92+
```
93+
94+
The result of such warnings is that certain information won't be statically available in Swift, e.g., the superclass won't be known (so we will assume it is `JavaObject`), or the specified constructors or methods won't be translated. If you don't need these APIs, the warnings can be safely ignored. The APIs can still be called dynamically via JNI.
95+
96+
The `--jar` option changes the operation of `swift-java`. Instead of wrapping Java classes in Swift, it scans the given input Jar file to find all public classes and outputs a configuration file `swift-java.config` mapping all of the Java classes in the Jar file to Swift types. The `--jar` mode is expected to be used to help import a Java library into Swift wholesale, after which swift-java should invoked again given the generated configuration file.
97+
98+
### Under construction: Create a Java class to wrap the Swift library
99+
100+
**NOTE**: the instructions here work, but we are still smoothing out the interoperability story.
101+
102+
All JavaKit-based applications start execution within the Java Virtual Machine. First, define your own Java class that loads your native Swift library and provides a `native` entry point to get into the Swift code. Here is a minimal Java class that has all of the program's logic written in Swift, including `main`:
103+
104+
105+
```java
106+
package org.swift.javakit;
107+
108+
public class HelloSwiftMain {
109+
static {
110+
System.loadLibrary("HelloSwift");
111+
}
112+
113+
public native static void main(String[] args);
114+
}
115+
```
116+
117+
Compile this into a `.class` file with `javac` before we build the Swift half, e.g.,:
118+
119+
```
120+
javac Java/src/org/swift/javakit/JavaClassTranslator.java
121+
```
122+
123+
### Create a Swift library
124+
125+
The Java class created above loads a native library `HelloSwift` that needs to contain a definition of the `main` method in the class `org.swift.javakit.HelloSwiftMain`. `HelloSwift` should be defined as a SwiftPM dynamic library product, e.g.,
126+
127+
```swift
128+
products: [
129+
.library(
130+
name: "HelloSwift",
131+
type: .dynamic,
132+
targets: ["HelloSwift"]
133+
),
134+
]
135+
```
136+
137+
with an associated target that depends on `JavaKit`:
138+
139+
```swift
140+
.target(
141+
name: "HelloSwift",
142+
dependencies: [
143+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
144+
.product(name: "JavaKit", package: "JavaKit")
145+
])
146+
```
147+
148+
### Implement the `native` Java method in Swift
149+
Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined:
150+
151+
```swift
152+
import JavaKit
153+
154+
@JavaImplementation("org.swift.javakit.HelloSwiftMain")
155+
struct HelloSwiftMain {
156+
@JavaStaticMethod
157+
static func main(arguments: [String], environment: JNIEnvironment? = nil) {
158+
print("Command line arguments are: \(arguments)")
159+
}
160+
}
161+
```
162+
163+
Go ahead and build this library with `swift build`, and find the path to the directory containing the resulting shared library (e.g., `HelloSwift.dylib`, `HelloSwift.so`, or `HelloSwift.dll`, depending on platform). It is often in `.build/debug/` if you ran `swift build` on the command line.
164+
165+
### Putting it all together!
166+
167+
Finally, run this program on the command line like this:
168+
169+
```
170+
java -cp Java/src -Djava.library.path=$(PATH_CONTAINING_HELLO_SWIFT)/ org.swift.javakit.HelloSwiftMain -v argument
171+
```
172+
173+
This will prints the command-line arguments `-v` and `argument` as seen by Swift.
174+
175+
### Bonus: Swift argument parser
176+
177+
The easiest way to build a command-line program in Swift is with the [Swift argument parser library](https://github.com/apple/swift-argument-parser). We can extend our `HelloSwiftMain` type to conform to `ParsableCommand` and using the Swift argument parser to process the arguments provided by Java:
178+
179+
```swift
180+
import ArgumentParser
181+
import JavaKit
182+
183+
@JavaClass("org.swift.javakit.HelloSwiftMain")
184+
struct HelloSwiftMain: ParsableCommand {
185+
@Option(name: .shortAndLong, help: "Enable verbose output")
186+
var verbose: Bool = false
187+
188+
@JavaImplementation
189+
static func main(arguments: [String], environment: JNIEnvironment? = nil) {
190+
let command = Self.parseOrExit(arguments)
191+
command.run(environment: environment)
192+
}
193+
194+
func run(environment: JNIEnvironment? = nil) {
195+
print("Verbose = \(verbose)")
196+
}
197+
}
198+
```
199+
200+
## Download Java dependencies in Swift builds: swift-java resolve
201+
202+
> TIP: See the `Samples/DependencySampleApp` for a fully functional showcase of this mode.
203+
204+
TODO: documentation on this feature
205+
206+
## Expose Swift code to Java: swift-java jextract
207+
208+
The project is still very early days, however the general outline of using this approach is as follows:
209+
210+
- **No code changes** need to be made to Swift libraries that are to be exposed to Java using jextract-swift.
211+
- Swift sources are compiled to `.swiftinterface` files
212+
- These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files
213+
- The generated Java files contain generated code for efficient native invocations.
214+
215+
You can then use Swift libraries in Java just by calling the appropriate methods and initializers.
216+
217+
### Generating Java bindings for Swift libraries
218+
219+
This repository also includes the `jextract-swift` tool which is similar to the JDK's [`jextract`](https://github.com/openjdk/jextract/).
220+
221+
This approach offers two modes of operation:
222+
223+
- the default `--mode ffm` which uses the [JEP-424 Foreign function and Memory APIs](https://openjdk.org/jeps/424) which are available since JDK **22**. It promises much higher performance than traditional approaches using JNI, and is primarily aimed for calling native code from a Java application.
224+
225+
> Tip: In order to use the ffm mode, you need to install a recent enough JDK (at least JDK 22). The recommended, and simplest way, to install the a JDK distribution of your choice is [sdkman](https://sdkman.io):
226+
>
227+
> ```
228+
> curl -s "https://get.sdkman.io" | bash
229+
> sdk install java 22-open
230+
>
231+
> export JAVA_HOME=$(sdk home java 22-open)
232+
> ```
233+
234+
`jextract-swift` can be pointed at `*.swiftinterface` files and will generate corresponding Java files that use the (new in Java 22) Foreign Function & Memory APIs to expose efficient ways to call "down" into Swift from Java.
235+
236+
## Default jextract behaviors
237+
238+
Only `public` functions, properties and types are imported.
239+
240+
Global Swift functions become static functions on on a class with the same name as the Swift module in Java,
241+
242+
```swift
243+
// Swift (Sources/SomeModule/Example.swift)
244+
245+
public func globalFunction()
246+
```
247+
248+
becomes:
249+
250+
```java
251+
// Java (SomeModule.java)
252+
253+
public final class SomeModule ... {
254+
public static void globalFunction() { ... }
255+
}
256+
```

0 commit comments

Comments
 (0)