Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/scripts/validate_docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
set -e
set -x

swift run generate-config-docs --check

./scripts/generate-cli-help-snippets.sh
if ! git diff --exit-code -- Snippets/SwiftJavaCLIHelp.txt; then
echo "::error::Snippets/SwiftJavaCLIHelp.txt is out of date with the swift-java CLI's --help output (likely a command's flags or abstract text changed). Run scripts/generate-cli-help-snippets.sh and commit the result."
exit 1
fi

DEPENDENCY='.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0")'

if grep -q "$DEPENDENCY" Package.swift; then
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:
jdk_vendor: ['corretto']
steps:
- uses: actions/checkout@v7
- name: Mark workspace as safe for git
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Prepare CI Environment
uses: ./.github/actions/prepare_env
- name: Cache .build
Expand Down
1 change: 1 addition & 0 deletions .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ gradlew.bat
**/gradlew.bat
**/ci-validate.sh
**/DO_NOT_EDIT.txt
Snippets/__DO_NOT_EDIT_SYMLINK_ONLY__
Plugins/**/_PluginsShared
Plugins/**/0_PLEASE_SYMLINK*
Plugins/PluginsShared/JavaKitConfigurationShared
Expand Down
25 changes: 14 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Please ensure to specify the following:
* Commit hash
* Contextual information (e.g. what you were trying to achieve with swift-java)
* Simplest possible steps to reproduce
* More complex the steps are, lower the priority will be.
* The more complex the steps, the lower the priority.
* A pull request with failing test case is preferred, but it's just fine to paste the test case into the issue description.
* Anything that might be relevant in your opinion, such as:
* Swift version or the output of `swift --version`
Expand All @@ -24,7 +24,7 @@ Please ensure to specify the following:
Commit hash: b17a8a9f0f814c01a56977680cb68d8a779c951f

Context:
While testing my application that uses with swift-openapi-generator, I noticed that ...
While testing my application that uses swift-java, I noticed that ...

Steps to reproduce:
1. ...
Expand Down Expand Up @@ -78,7 +78,7 @@ reflected in your working directory:
% act --bind workflow_call -j soundness --input format_check_enabled=true
```

If you'd like `act` to always run with certain flags, these can be be placed in
If you'd like `act` to always run with certain flags, these can be placed in
an `.actrc` file either in the current working directory or your home
directory, for example:

Expand All @@ -88,20 +88,23 @@ directory, for example:
--action-offline-mode
```

For frequent contributors, we recommend adding the script as a [git pre-push hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks), which you can do via executing the following command in the project root directory:
For frequent contributors, we recommend running the soundness checks before pushing. You can wire that up as a [git pre-push hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks):

```bash
cat << EOF > .git/hooks/pre-push

if [[ -f "scripts/soundness.sh" ]]; then
scripts/soundness.sh
fi
cat << 'EOF' > .git/hooks/pre-push
#!/bin/sh
exec act workflow_call -j soundness --input format_check_enabled=true --input shell_check_enabled=true
EOF
chmod +x .git/hooks/pre-push
```

Which makes the script execute, and only allow the `git push` to complete if the check has passed.
This only allows the `git push` to complete if the checks pass.

In the case of formatting issues, `git add` the formatting changes and attempt the push again.

### Regenerating generated docs

In the case of formatting issues, you can then `git add` the formatting changes, and attempt the push again.
Some documentation is generated from source and checked in, so CI can catch it drifting out of date. If you changed `Configuration.swift` or a CLI command's flags/help text, run `scripts/generate-docs.sh` and commit the generated changes.

## How to contribute your work

Expand Down
24 changes: 22 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ if let localPath = Context.environment["SWIFT_JAVA_JNI_CORE_PATH"] {
swiftJavaJNICoreDep = .package(url: "https://github.com/swiftlang/swift-java-jni-core", branch: "main")
}

// Set SWIFTJAVA_DOCC_PLUGIN_INSTALL=1 to install the docc-plugin automatically.
// Set SWIFTJAVA_DOCC_PLUGIN_INSTALL=1 to install the docc-plugin automatically,
// or DOCC_PLUGIN_PATH=<path> to point at a local checkout.
// This is a workaround because swift-subprocess includes the plugin explicitly,
// which breaks tools trying to add `swift package add-dependency` the plugin
// to swift-java because it thinks the plugin was already added, but it is not.
let extraDependencies: [Package.Dependency]
if Context.environment["SWIFTJAVA_DOCC_PLUGIN_INSTALL"] == "1" {
if let localPath = Context.environment["DOCC_PLUGIN_PATH"] {
extraDependencies = [.package(path: localPath)]
} else if Context.environment["SWIFTJAVA_DOCC_PLUGIN_INSTALL"] == "1" {
extraDependencies = [
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.5.0")
]
Expand Down Expand Up @@ -429,6 +432,23 @@ let package = Package(
]
),

// Dev-time tool: regenerates the "Supported configuration options" table in
// `Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaConfigFile.md`
// straight from the doc comments on `Configuration.swift` and the enums it
// references, so the two never drift apart.
//
// Run manually via `swift run generate-config-docs`; CI validates freshness
// with `swift run generate-config-docs --check`.
.executableTarget(
name: "generate-config-docs",
dependencies: [
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
path: "Sources/GenerateConfigDocs"
),

.plugin(
name: "_StaticBuildConfigPlugin",
capability: .buildTool(),
Expand Down
101 changes: 50 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ This project contains tools and libraries that facilitate **Swift & Java Interop

## Introduction

If you'd like to check out a quick introduction to Swift & Java interoperability, you may be interested in this presentation from WWDC25: [WWDC25: Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA).
For a quick introduction to Swift & Java interoperability, see the WWDC25 session [Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA).

While we work on more quickstarts and documentation, please refer to the Sample projects located in [Samples/](Samples/) that showcase the various ways you can use swift-java in your Swift or Java projects.
While we work on more quickstarts and documentation, refer to the sample projects in [Samples/](Samples/), which showcase the various ways to use swift-java in Swift or Java projects.

## Dependencies

Expand Down Expand Up @@ -42,18 +42,17 @@ sdk install java 25.0.1-amzn

## Self-publish supporting Java libraries

Swift-java relies on supporting libraries that are under active development and not yet published to Maven Central. To use the project, you'll need to self-publish these libraries locally so your Java project can depend on them.
swift-java relies on supporting libraries that are under active development and not yet published to Maven Central. To use the project, you'll need to self-publish these libraries locally so your Java project can depend on them.

To publish the libraries to your local maven repository (`$HOME/.m2`), you can run:
To publish the libraries to your local maven repository (`$HOME/.m2`), run this in `swift-java/`:

```
// in swift-java/
```bash
./gradlew publishToMavenLocal
```

To consume these libraries in your Java project built using Gradle, you can then include the local repository in the repositories to resolve dependencies from:
To consume these libraries in your Java project built using Gradle, include the local repository in the repositories to resolve dependencies from:

```
```kotlin
repositories {
mavenLocal()
mavenCentral()
Expand All @@ -62,65 +61,66 @@ repositories {

We anticipate simplifying this in the future.

## SwiftJava macros
## SwiftJava macros: calling Java from Swift

SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift.
SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand", and calling Java code from Swift.

It is possible to generate Swift bindings to Java libraries using SwiftJava by using the `swift-java wrap-java` command.
You can also generate Swift bindings to Java libraries with the `swift-java wrap-java` command.

Required language/runtime versions:
- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration
- **Swift 6.2.x**, because the library uses modern Swift macros

**swift-java jextract**
## swift-java jextract: calling Swift from Java

`jextract` is a source generator which **generates Java bindings to existing Swift libraries**.
Its inputs are Swift sources or packages, and its outputs are the generated Swift and Java code needed to call those functions efficiently from Java.

Is a source generator which will **generate Java bindings to existing Swift libraries**.
Its inputs are Swift sources or packages, and outputs are generated Swift and Java code necessary to call these functions efficiently from Java.
It has two modes, `ffm` and `jni`.

## swift-java jextract --mode=ffm (default)
### swift-java jextract --mode=ffm (default)

This mode provides the most flexibility and performance, and allows to decrease the amount of data being copied between Swift and Java.
This does require the use of the relatively recent [JEP-454: Foreign Function & Memory API](https://openjdk.org/jeps/454), which is only available since JDK22, and will become part of JDK LTS releases with JDK 25 (depending on your JDK vendor).
This mode offers the most flexibility and performance, and can reduce the amount of data copied between Swift and Java.
It requires [JEP-454: Foreign Function & Memory API](https://openjdk.org/jeps/454), which is final since JDK 22.

This is the primary way we envision calling Swift code from server-side Java libraries and applications.

Required language/runtime versions:
- **Swift 6.2**, because of dependence on rich swift interface files
- **JDK 25+**
- We are validating the implementation using the currently supported non-LTE release, which at present means JDK-25.
- We validate the implementation against the currently supported non-LTS release, which at present means JDK 25.

## swift-java jextract --mode=jni
### swift-java jextract --mode=jni

In this mode, the generated sources will use the legacy JNI approach to calling native code.
In this mode the generated sources use JNI to call native code.

This mode is more limited in some performance and flexibility that it can offer, however it is the most compatible, since even very old JVM's as well as even Android systems can be supported by this mode.
We recommend this mode when FFM is not available, or wide ranging deployment compatibility is your priority. When performance is paramaunt, we recommend the FFM mode instead.
This mode offers less performance and flexibility than FFM, but it is the most compatible: it works on older JVMs as well as on Android.
Use it when FFM is not available, or when wide deployment compatibility is your priority. When performance is paramount, prefer FFM.

Required language/runtime versions:
- **Swift 6.2**, because of dependence on rich swift interface files
- **Java 7+**, including

- **Swift 6.2**, because of dependence on rich swift interface files
- **JDK 17+**; the generated Java sources target the `javaSourceLevel` setting, which supports 17 through 25 (default 22)

## Development and Testing

This project contains multiple builds, living side by side together.

You will need to have:
- Swift (6.2.x+)
- Java (25+ for FFM, even though we support lower JDK targets)
- Java (25+ to build this project, even though the published libraries target lower JDK versions)
- Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`)

### Preparing your environment

Install **Swift**, the easiest way to do this is to use **Swiftly**: [swift.org/install/](https://www.swift.org/install/).
This should automatically install a recent Swift, but you can always make sure by running:
Install **Swift**; the easiest way is [Swiftly](https://www.swift.org/install/).
This installs a recent Swift, but you can pin the version explicitly:

```bash
swiftly install 6.2 --use
```

Install a recent enough Java distribution. We validate this project using Corretto so you can choose to use that as well,
however any recent enough Java distribution should work correctly. You can use sdkman to install Java:
Install a recent enough Java distribution. We validate this project using Corretto, so you may want to use that as well,
though any recent enough distribution should work. You can use sdkman to install Java:

```bash
# Install sdkman from: https://sdkman.io
Expand All @@ -142,52 +142,51 @@ export JAVA_HOME="$(sdk home java current)"

### Testing your changes

Many tests, including source generation tests, are written in Swift and you can execute them all by running the
swift package manager test command:
Many tests, including source generation tests, are written in Swift. Run them all with:

```bash
> swift test
```

When adding tests in `Tests/...` targets, you can run these tests (or filter a specific test using `swift test --filter type-or-method-name`).

Some tests are implemented in Java and therefore need to be executed using Gradle.
Please always use the gradle wrapper (`./gradlew`) to make sure to use the appropriate Gradle version
Some tests are implemented in Java and need to be executed using Gradle.
Always use the Gradle wrapper (`./gradlew`) so the correct Gradle version is used:

```bash
> ./gradlew test
```

> Tip: A lot of the **runtime tests** for code relying on `jextract` are **located in sample apps**,
> so if you need to runtime test any code relying on source generation steps of jextract, consider adding the tests
> to an appropriate Sample. These tests are also executed in CI (which you can check in the `ci-validate.sh` script
> contained in every sample repository).
> Tip: Many of the **runtime tests** for code relying on `jextract` are **located in sample apps**,
> so if you need to runtime test code that depends on jextract's source generation, consider adding the tests
> to an appropriate Sample. These tests also run in CI; see the `ci-validate.sh` script in each sample
> that has one.

### Sample apps & tests

Sample apps are located in the `Samples/` directory, and they showcase full "roundtrip" usage of the library and/or tools.

Samples are build by default by Gradle. Building samples can be skipped by appending the flag `-PskipSamples=true` to a gradle command.
Samples are built by default by Gradle. Building samples can be skipped by appending the flag `-PskipSamples=true` to a gradle command.

#### SwiftJava (Swift -> Java)
#### SwiftJava (calling Java from Swift)

To run a simple app showcasing a Swift process calling into a Java library you can run:
To run a simple app showcasing a Swift process calling into a Java library:

```bash
cd Samples/SwiftJavaExtractFFMSampleApp
./ci-validate.sh # which is just `swift build` and a `java -cp ...` invocation of the compiled program
cd Samples/JavaProbablyPrime
./ci-validate.sh # which is just a `swift run JavaProbablyPrime 1337`
```

#### jextract (Java -> Swift)
#### jextract (calling Swift from Java)

To run a simple example app showcasing the jextract (Java calling Swift) approach you can:
To run a simple example app showcasing a Java program calling into Swift:

```bash
./gradlew Samples:SwiftJavaExtractFFMSampleApp:run
```

This will also generate the necessary sources (by invoking jextract, extracting the `Sources/ExampleSwiftLibrary`)
and generating Java sources in `src/generated/java`.
This also generates the necessary sources (by invoking jextract on `Sources/MySwiftLibrary`)
and the Java sources in `src/generated/java`.

#### Other sample apps

Expand All @@ -197,7 +196,7 @@ Please refer to the [Samples](Samples) directory for more sample apps which show

You can run Swift [ordo-one/package-benchmark](https://github.com/ordo-one/package-benchmark) and OpenJDK [JMH](https://github.com/openjdk/jmh) benchmarks in this project.

Swift benchmarks are located under `Benchmarks/` and JMH benchmarks are currently part of the SwiftKit sample project: `Samples/SwiftJavaExtractFFMSampleApp/src/jmh` because they depend on generated sources from the sample.
Swift benchmarks are located under `Benchmarks/`. JMH benchmarks live in `Samples/SwiftJavaExtractFFMSampleApp/src/jmh`, because they depend on sources generated by that sample.

### Swift benchmarks

Expand All @@ -217,7 +216,7 @@ cd Samples/SwiftJavaExtractFFMSampleApp
./gradlew jmh
```

Please read documentation of both performance testing tools and understand that results must be interpreted and not just taken at face value. Benchmarking is tricky and environment sensitive task, so please be careful when constructing and reading benchmarks and their results. If in doubt, please reach out on the forums.
Please read the documentation of both performance testing tools, and note that results must be interpreted rather than taken at face value. Benchmarking is tricky and environment sensitive, so be careful when constructing and reading benchmarks and their results. If in doubt, reach out on the forums.

## User Guide

Expand All @@ -241,6 +240,6 @@ xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc

## Project Status

**This project is under active development. We welcome feedback about any issues you encounter.**
**This project is under active development. We welcome feedback about any issues you encounter.**

There is no guarantee about API stability until the project reaches a 1.0 release.
There is no guarantee of API stability until the project reaches a 1.0 release.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ print("Start Sample app...")
// TODO: locating the classpath is more complex, need to account for dependencies of our module
let swiftJavaClasspath = findSwiftJavaClasspaths() // scans for .classpath files

// snippet.dependencyUsage
// 1) Start a JVM with appropriate classpath
let jvm = try JavaVirtualMachine.shared(classpath: swiftJavaClasspath)

Expand All @@ -50,5 +51,6 @@ for record in try CSVFormatClass.RFC4180.parse(reader)!.getRecords()! {
print("Field: \(field)")
}
}
// snippet.end

print("Done.")
4 changes: 3 additions & 1 deletion Samples/JavaKitSampleApp/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ let package = Package(
],

dependencies: [
.package(name: "swift-java", path: "../../")
.package(name: "swift-java", path: "../../"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
],

targets: [
Expand All @@ -32,6 +33,7 @@ let package = Package(
.product(name: "SwiftJava", package: "swift-java"),
.product(name: "JavaUtilFunction", package: "swift-java"),
.product(name: "JavaUtilJar", package: "swift-java"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
swiftSettings: [
.swiftLanguageMode(.v5)
Expand Down
Loading
Loading