Skip to content

Commit 342b5ea

Browse files
authored
docc documentation, first step (#319)
1 parent f62a317 commit 342b5ea

File tree

14 files changed

+595
-264
lines changed

14 files changed

+595
-264
lines changed

.github/actions/prepare_env/action.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ runs:
2626
elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then
2727
echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV
2828
fi
29-
- name: Check Java environment
30-
shell: bash
31-
run: ./gradlew -q javaToolchains
29+
# - name: Check Java environment
30+
# shell: bash
31+
# run: ./gradlew -q javaToolchains
3232
- name: Cache local SwiftPM repository
3333
if: matrix.os_version == 'jammy'
3434
uses: actions/cache@v4

.github/scripts/install_swiftly.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ else
4141

4242
curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && pkgutil --check-signature swiftly.pkg && pkgutil --verbose --expand swiftly.pkg "${SWIFTLY_HOME_DIR}" && tar -C "${SWIFTLY_HOME_DIR}" -xvf "${SWIFTLY_HOME_DIR}"/swiftly-*/Payload && "$SWIFTLY_HOME_DIR/bin/swiftly" init -y --skip-install
4343

44+
chmod +x "$SWIFTLY_HOME_DIR/env.sh"
4445
# shellcheck disable=SC1091
4546
. "$SWIFTLY_HOME_DIR/env.sh"
4647
fi
@@ -84,5 +85,8 @@ echo "Displaying swift version"
8485
swiftly run "${runSelector[@]}" swift --version
8586

8687
if [[ "$(uname -s)" == "Linux" ]]; then
87-
CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh"
88+
if [[ -f "$(dirname "$0")/install-libarchive.sh" ]]; then
89+
CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh"
90+
fi
8891
fi
92+

.github/scripts/validate_docs.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
set -e
4+
set -x
5+
6+
cat <<EOF >> Package.swift
7+
8+
package.dependencies.append(
9+
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0")
10+
)
11+
EOF
12+
13+
swift package --disable-sandbox plugin generate-documentation --target "SwiftJavaDocumentation" --warnings-as-errors --analyze

.github/workflows/pull_request.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ jobs:
1313
# FIXME: Something is off with the format task and it gets "stuck", need to investigate
1414
format_check_enabled: false
1515
license_header_check_project_name: Swift.org
16+
# Since we need JAVA_HOME to be set up for building the project and depending on a different workflow won't
17+
# give us that, we disable the checking and make a separate job that performs docs validation
18+
docs_check_enabled: false
19+
20+
# This replicates 'docs-check' from https://github.com/swiftlang/github-workflows/blob/main/.github/workflows/soundness.yml
21+
# because we need to set up environment so we can build the SwiftJava project (including Java runtime/dependencies).
22+
soundness-docs:
23+
name: Documentation check
24+
runs-on: ubuntu-latest
25+
container:
26+
image: 'swift:6.1-noble'
27+
strategy:
28+
fail-fast: true
29+
matrix:
30+
swift_version: ['6.1.2']
31+
os_version: ['jammy']
32+
jdk_vendor: ['corretto']
33+
steps:
34+
- uses: actions/checkout@v4
35+
- name: Prepare CI Environment
36+
uses: ./.github/actions/prepare_env
37+
- name: Swift Build
38+
run: swift build
39+
- name: Run documentation check
40+
run: ./.github/scripts/validate_docs.sh
1641

1742
test-java:
1843
name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}})

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ Package.resolved
4444
*/**/*.o
4545
*/**/*.swiftdeps
4646
*/**/*.swiftdeps~
47+
*/**/.docc-build/

.spi.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 1
2+
builder:
3+
configs:
4+
- documentation_targets: [
5+
SwiftJavaDocumentation,
6+
JavaKit,
7+
SwiftKitSwift
8+
]

Package.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ let package = Package(
139139
name: "swift-java",
140140
targets: ["SwiftJavaTool"]
141141
),
142+
143+
144+
.library(
145+
name: "SwiftJavaDocumentation",
146+
targets: ["SwiftJavaDocumentation"]
147+
),
142148

143149
// ==== Plugin for building Java code
144150
.plugin(
@@ -198,6 +204,14 @@ let package = Package(
198204
.package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")),
199205
],
200206
targets: [
207+
.target(
208+
name: "SwiftJavaDocumentation",
209+
dependencies: [
210+
"JavaKit",
211+
"SwiftKitSwift",
212+
]
213+
),
214+
201215
.macro(
202216
name: "JavaKitMacros",
203217
dependencies: [

USER_GUIDE.md renamed to Sources/JavaKit/Documentation.docc/JavaKit.md

Lines changed: 3 additions & 251 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
Library and tools to make it easy to use Java libraries from Swift using the Java Native Interface (JNI).
44

5-
## Getting started
6-
7-
Before using this package, set the `JAVA_HOME` environment variable to point at your Java installation. Failing to do so will produce errors when processing the package manifest. Alternatively, you can put the path to your Java installation in the file `~/.java_home`.
8-
9-
### Using Java libraries from Swift
5+
## JavaKit: Using Java libraries from Swift
106

117
Existing Java libraries can be wrapped for use in Swift with the `swift-java`
128
tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `swift-java.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`:
@@ -282,7 +278,7 @@ Java native methods that throw any checked exception should be marked as `throws
282278

283279
The Swift implementations of Java `native` constructors and static methods require an additional Swift parameter `environment: JNIEnvironment? = nil`, which will receive the JNI environment in which the function is being executed. In case of nil, the `JavaVirtualMachine.shared().environment()` value will be used.
284280

285-
## Using Java libraries from Swift
281+
## JavaKit: Using Java libraries from Swift
286282

287283
This section describes how Java libraries and mapped into Swift and their use from Swift.
288284

@@ -420,7 +416,7 @@ extension JavaClass<URLConnection> {
420416
}
421417
```
422418

423-
### Interfaces
419+
### Java Interfaces
424420

425421
Java interfaces are similar to classes, and are projected into Swift in much the same way, but with the macro `JavaInterface`. The `JavaInterface` macro takes the Java interface name as well as any Java interfaces that this interface extends. As an example, here is the Swift projection of the [`java.util.Enumeration`](https://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) generic interface:
426422

@@ -437,247 +433,3 @@ public struct Enumeration<E: AnyJavaObject> {
437433
public func nextElement() -> JavaObject!
438434
}
439435
```
440-
441-
## Translating Java classes with `swift-java`
442-
443-
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
444-
single Java class. The `swift-java` can be executed like this:
445-
446-
```
447-
swift-java
448-
```
449-
450-
to produce help output like the following:
451-
452-
```
453-
OVERVIEW: Generate sources and configuration for Swift and Java interoperability.
454-
455-
USAGE: swift-java <options> <subcommand>
456-
457-
OPTIONS:
458-
--depends-on <depends-on>
459-
A Java2Swift configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options for each Swift module that this
460-
module depends on (transitively) that contains wrapped Java sources.
461-
--jar Specifies that the input is a Jar file whose public classes will be loaded. The output of Java2Swift will be a configuration file (Java2Swift.config) that can be used as input to a subsequent Java2Swift invocation to
462-
generate wrappers for those public classes.
463-
--fetch Fetch dependencies from given target (containing swift-java configuration) or dependency string
464-
--swift-native-implementation <swift-native-implementation>
465-
The names of Java classes whose declared native methods will be implemented in Swift.
466-
--output-swift <output-swift>
467-
The directory where generated Swift files should be written. Generally used with jextract mode.
468-
--output-java <output-java>
469-
The directory where generated Java files should be written. Generally used with jextract mode.
470-
--java-package <java-package>
471-
The Java package the generated Java code should be emitted into.
472-
-c, --cache-directory <cache-directory>
473-
Directory where to write cached values (e.g. swift-java.classpath files)
474-
-o, --output-directory <output-directory>
475-
The directory in which to output the generated Swift files or the SwiftJava configuration file.
476-
--input-swift <input-swift>
477-
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.
478-
-l, --log-level <log-level>
479-
Configure the level of logs that should be printed (values: trace, debug, info, notice, warning, error, critical; default: log level)
480-
--cp, --classpath <cp> Class search path of directories and zip/jar files from which Java classes can be loaded.
481-
-f, --filter-java-package <filter-java-package>
482-
While scanning a classpath, inspect only types included in this package
483-
-h, --help Show help information.
484-
485-
SUBCOMMANDS:
486-
configure Configure and emit a swift-java.config file based on an input dependency or jar file
487-
resolve Resolve dependencies and write the resulting swift-java.classpath file
488-
489-
See 'swift-java help <subcommand>' for detailed help.
490-
```
491-
492-
For example, the `JavaKitJar` library is generated with this command line:
493-
494-
```swift
495-
swift run swift-java --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config
496-
```
497-
498-
The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated.
499-
500-
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
501-
`JavaKitNetwork`'s configuration file as a dependency here.
502-
503-
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.
504-
505-
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
506-
`java.util.zip.ZipOutputStream` and `java.io.OutputStream`:
507-
508-
```
509-
warning: Unable to translate 'java.util.jar.JarOutputStream' superclass: Java class 'java.util.zip.ZipOutputStream' has not been translated into Swift
510-
warning: Unable to translate 'java.util.jar.JarOutputStream' constructor: Java class 'java.io.OutputStream' has not been translated into Swift
511-
warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo': Java class 'java.io.OutputStream' has not been translated into Swift
512-
```
513-
514-
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.
515-
516-
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.
517-
518-
### Under construction: Create a Java class to wrap the Swift library
519-
520-
**NOTE**: the instructions here work, but we are still smoothing out the interoperability story.
521-
522-
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`:
523-
524-
525-
```java
526-
package org.swift.javakit;
527-
528-
public class HelloSwiftMain {
529-
static {
530-
System.loadLibrary("HelloSwift");
531-
}
532-
533-
public native static void main(String[] args);
534-
}
535-
```
536-
537-
Compile this into a `.class` file with `javac` before we build the Swift half, e.g.,:
538-
539-
```
540-
javac Java/src/org/swift/javakit/JavaClassTranslator.java
541-
```
542-
543-
### Create a Swift library
544-
545-
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.,
546-
547-
```swift
548-
products: [
549-
.library(
550-
name: "HelloSwift",
551-
type: .dynamic,
552-
targets: ["HelloSwift"]
553-
),
554-
]
555-
```
556-
557-
with an associated target that depends on `JavaKit`:
558-
559-
```swift
560-
.target(
561-
name: "HelloSwift",
562-
dependencies: [
563-
.product(name: "ArgumentParser", package: "swift-argument-parser"),
564-
.product(name: "JavaKit", package: "JavaKit")
565-
])
566-
```
567-
568-
### Implement the `native` Java method in Swift
569-
Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined:
570-
571-
```swift
572-
import JavaKit
573-
574-
@JavaImplementation("org.swift.javakit.HelloSwiftMain")
575-
struct HelloSwiftMain {
576-
@JavaStaticMethod
577-
static func main(arguments: [String], environment: JNIEnvironment? = nil) {
578-
print("Command line arguments are: \(arguments)")
579-
}
580-
}
581-
```
582-
583-
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.
584-
585-
### Putting it all together!
586-
587-
Finally, run this program on the command line like this:
588-
589-
```
590-
java -cp Java/src -Djava.library.path=$(PATH_CONTAINING_HELLO_SWIFT)/ org.swift.javakit.HelloSwiftMain -v argument
591-
```
592-
593-
This will prints the command-line arguments `-v` and `argument` as seen by Swift.
594-
595-
### Bonus: Swift argument parser
596-
597-
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:
598-
599-
```swift
600-
import ArgumentParser
601-
import JavaKit
602-
603-
@JavaClass("org.swift.javakit.HelloSwiftMain")
604-
struct HelloSwiftMain: ParsableCommand {
605-
@Option(name: .shortAndLong, help: "Enable verbose output")
606-
var verbose: Bool = false
607-
608-
@JavaImplementation
609-
static func main(arguments: [String], environment: JNIEnvironment? = nil) {
610-
let command = Self.parseOrExit(arguments)
611-
command.run(environment: environment)
612-
}
613-
614-
func run(environment: JNIEnvironment? = nil) {
615-
print("Verbose = \(verbose)")
616-
}
617-
}
618-
```
619-
620-
# `jextract-swift`
621-
622-
The project is still very early days, however the general outline of using this approach is as follows:
623-
624-
- **No code changes** need to be made to Swift libraries that are to be exposed to Java using jextract-swift.
625-
- Swift sources are compiled to `.swiftinterface` files
626-
- These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files
627-
- The generated Java files contain generated code for efficient native invocations.
628-
629-
You can then use Swift libraries in Java just by calling the appropriate methods and initializers.
630-
631-
## `jextract-swift`: Generating Java bridging files
632-
633-
This repository also includes the `jextract-swift` tool which is similar to the JDK's [`jextract`](https://github.com/openjdk/jextract/).
634-
635-
This approach is using Java's most recent (stable in JDK22) Foreign function and Memory APIs, collectively known as "Project Panama". You can read more about it here: https://openjdk.org/projects/panama/ It promises much higher performance than traditional approaches using JNI, and is primarily aimed for calling native code from a Java application.
636-
637-
:warning: This feature requires JDK 22. The recommended way to install/manage JDKs is using [sdkman](https://sdkman.io):
638-
639-
```
640-
curl -s "https://get.sdkman.io" | bash
641-
sdk install java 22-open
642-
643-
export JAVA_HOME=$(sdk home java 22-open)
644-
```
645-
646-
`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.
647-
648-
## JExtract: Swift <-> Java Type mapping
649-
650-
TODO: these are not implemented yet.
651-
652-
### Closures and Callbacks
653-
654-
A Swift function may accept a closure which is used as a callback:
655-
656-
```swift
657-
func callMe(maybe: () -> ()) {}
658-
```
659-
660-
Minimal support for c-compatible closures is implemented, more documentation soon.
661-
662-
663-
## `jextract-swift` importer behavior
664-
665-
Only `public` functions, properties and types are imported.
666-
667-
Global Swift functions become static functions on on a class with the same name as the Swift module in Java,
668-
669-
```swift
670-
// Swift (Sources/SomeModule/Example.swift)
671-
672-
public func globalFunction()
673-
```
674-
675-
becomes:
676-
677-
```java
678-
// Java (SomeModule.java)
679-
680-
public final class SomeModule ... {
681-
public static void globalFunction() { ... }
682-
}
683-
```

0 commit comments

Comments
 (0)