Open33

swift-java 入門

ykwsykws

Java から C で書く必要な場面でよりメモリセーフな Swift のコードを利用したい

ykwsykws

Swift から Java を利用するのには JavaKit を使う

Java から Swift を利用するには jextract-swift を使う
swiftc の代わりに jextract-swift で Wrapper class を生成して javac から使えるようにする

ykwsykws

jextract-swift

  • Swift 6.x development snapshots
  • JDK 22+
ykwsykws

サンプルを実行すると再定義されてエラーになる

Samples/JavaKitSampleApp/.build/arm64-apple-macosx/debug/JavaRuntime-tool.build/module.modulemap:1:8: error: redefinition of module 'JavaRuntime'
1 | module JavaRuntime {
  |        `- error: redefinition of module 'JavaRuntime'
ykwsykws

jextract-swift の要求する JDK は 22 に限定されている
22未満は当然として、23も toolchain が対応していなかった
以下を書き換えただけでは依存関係を解消できなかった

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(22))
    }
}
ykwsykws

SDKMAN で JDK 22 をインストールしようと思ったら、 Oracle 一択になる
他の Distribution は 22 は LTS ではないため、配布していない模様

 Oracle        |     | 24.0.1       | oracle  |            | 24.0.1-oracle       
               |     | 24           | oracle  |            | 24-oracle           
               |     | 23.0.2       | oracle  |            | 23.0.2-oracle       
               | >>> | 22.0.2       | oracle  | installed  | 22.0.2-oracle       
               |     | 21.0.7       | oracle  |            | 21.0.7-oracle       
               |     | 21.0.6       | oracle  |            | 21.0.6-oracle       
               |     | 17.0.12      | oracle  |            | 17.0.12-oracle   
ykwsykws

Java から Swift のコードを実行するサンプルコードに対して、 Swift のコードを書き換えて期待する出力がされることを確認できた

[swift][MySwiftLibrary/MySwiftLibrary.swift:27](helloWorld()) helloWorld() swift-java java -> swift!!!
ykwsykws

19:36 くらい swift-java の話 Ben Cohen, Swift @ Apple

https://www.youtube.com/watch?v=wn6C_XEv1Mo&t=1205s

10年前の9月 Swift は Objective-C とシームレスに相互運用できたので、 Swift は生産性を高めることができた
既存のコードベースで新しい言語への移行を成功させる上で不可欠
全部書き直そうとするとして失敗したプロジェクトは数多く存在する
段階的なアプローチを取る方が賢明である

社会的な問題もあるというくだりがよくわからなかった
一気に置き換えの場合に、古いコードベースと新しいコードベースのチームが分かれる
そこにダイナミックスが生まれなくなる
ましてや古い方をメンテナスしたいメンバーの方が稀で偏りが生まれる可能性がある
段階的であれば、ワンチームで既存のコードと新しいコードの両方を見ていく
問題ない部分はそのままで、パフォーマンスに問題のある部分だけ新しいコードに書き直すことができる

1 million Swift apps
Objective-C から Swift に移行が進んでいる

サーバは C または C++ こちらは Swift と相互運用が可能
Java もある
メモリ使用量に関して過去20年間でかなり進歩したが、それでも課題となるケースがある

Apple 1日に数十億件ものリクエストを処理する Java サービスサイドコードが多数存在する
Swift に移行することでこれを軽減できることはわかっている
Java の相互運用性を Swift にもたらす取り組みを開始した

swift-java は OSS としてオープンに活動する
特に Java の専門家の参加を期待している

ykwsykws

Swift -> Java

import JavaKit
import JavaRuntime

JavaRuntime

これにより Swift から Java オブジェクトのメソッドが呼べる
Objective-C メソッドセレクターでアクセスできることと似ている

JavaKit

Java にアクセスするためのツールキットで JNI をラップし、よりシームレスに
アノテーションでマクロ実装になっている

@JavaClass("com.example.swift.HelloSwift")
struct HelloSwift {
  @JavaMethod
  init(environment: JNIEnvironment)

  @JavaMethod
  func sayHelloBlock(_ i: Int32) -> Double

  @ImplementsJava
  func sayHello(i: Int32, _ j: Int32) -> Int32 {
  • Swift -> Java: Forward Interop
  • Java -> Swift: Reverse Interop

Interop(=Interoperability)

public native int sayHello(int x, int y);

native キーワード Java の実装は Swift の @ImplementsJava で行われる?

JavaKit には Swift からみた JVM のメモリ管理についてまだ課題はある

ykwsykws

Java, 特にパフォーマンスに詳しい人に OSS に参加してほしい

ykwsykws

Xcode26 beta3 で検証する
https://developer.apple.com/jp/support/install-beta/

  • Xcode 26.0 beta 3(17A5276g)
  • Command Line Tools for Xcode 26 beta 3
% swift --version 
swift-driver version: 1.127.8 Apple Swift version 6.2 (swiftlang-6.2.0.13.10 clang-1700.3.13.4)
Target: arm64-apple-macosx26.0
ykwsykws
% xcrun --toolchain swift swift --version
swift-driver version: 1.127.8 Apple Swift version 6.2 (swiftlang-6.2.0.13.10 clang-1700.3.13.4)
Target: arm64-apple-macosx26.0
ykwsykws

JDK 22 でビルドできなくなってた

% ./gradlew Samples:SwiftKitSampleApp:run
Downloading https://services.gradle.org/distributions/gradle-8.10-bin.zip
.............10%.............20%.............30%.............40%.............50%.............60%.............70%.............80%.............90%.............100%

Welcome to Gradle 8.10!

Here are the highlights of this release:
 - Support for Java 23
 - Faster configuration cache
 - Better configuration cache reports

For more details see https://docs.gradle.org/8.10/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Configure project :SwiftKitCore
Kotlin does not yet support 22 JDK target, falling back to Kotlin JVM_21 JVM target

> Task :BuildLogic:compileKotlin
Kotlin does not yet support 22 JDK target, falling back to Kotlin JVM_21 JVM target
w: Inconsistent JVM-target compatibility detected for tasks 'compileJava' (22) and 'compileKotlin' (21).
This will become an error in Gradle 8.0.
Consider using JVM Toolchain: https://kotl.in/gradle/jvm/toolchain
Learn more about JVM-target validation: https://kotl.in/gradle/jvm/target-validation 


FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':Samples:SwiftKitSampleApp:run'.
> Could not resolve all dependencies for configuration ':Samples:SwiftKitSampleApp:runtimeClasspath'.
   > Failed to calculate the value of task ':Samples:SwiftKitSampleApp:compileJava' property 'javaCompiler'.
      > Cannot find a Java installation on your machine matching this tasks requirements: {languageVersion=24, vendor=any vendor, implementation=vendor-specific} for MAC_OS on aarch64.
         > No locally installed toolchains match and toolchain download repositories have not been configured.

* Try:
> Learn more about toolchain auto-detection at https://docs.gradle.org/8.10/userguide/toolchains.html#sec:auto_detection.
> Learn more about toolchain repositories at https://docs.gradle.org/8.10/userguide/toolchains.html#sub:download_repositories.
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 1m 58s
12 actionable tasks: 12 executed

Kotlin?

Kotlin does not yet support 22 JDK target, falling back to Kotlin JVM_21 JVM target

ykwsykws

JDK 21 にしてもエラーになる

% ./gradlew Samples:SwiftKitSampleApp:run
Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details

FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':Samples:SwiftKitSampleApp:run'.
> Could not resolve all dependencies for configuration ':Samples:SwiftKitSampleApp:runtimeClasspath'.
   > Failed to calculate the value of task ':Samples:SwiftKitSampleApp:compileJava' property 'javaCompiler'.
      > Cannot find a Java installation on your machine matching this tasks requirements: {languageVersion=24, vendor=any vendor, implementation=vendor-specific} for MAC_OS on aarch64.
         > No locally installed toolchains match and toolchain download repositories have not been configured.

* Try:
> Learn more about toolchain auto-detection at https://docs.gradle.org/8.10/userguide/toolchains.html#sec:auto_detection.
> Learn more about toolchain repositories at https://docs.gradle.org/8.10/userguide/toolchains.html#sub:download_repositories.
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 21s
12 actionable tasks: 4 executed, 8 up-to-date

JDK 24?

Cannot find a Java installation on your machine matching this tasks requirements: {languageVersion=24, vendor=any vendor, implementation=vendor-specific} for MAC_OS on aarch64.

ykwsykws

JDK 24+ になってるね

JDK 24+
We are validating the implementation using the currently supported non-LTE release, which at present means JDK-24.

ykwsykws

JDK 24 でもエラーになる

% ./gradlew Samples:SwiftKitSampleApp:run
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/ykws/.gradle/wrapper/dists/gradle-8.10-bin/deqhafrv1ntovfmgh0nh3npr9/gradle-8.10/lib/native-platform-0.22-milestone-26.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled

Starting a Gradle Daemon, 2 incompatible Daemons could not be reused, use --status for details

> Task :buildSrc:compileGroovy FAILED
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/ykws/.gradle/wrapper/dists/gradle-8.10-bin/deqhafrv1ntovfmgh0nh3npr9/gradle-8.10/lib/native-platform-0.22-milestone-26.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':buildSrc:compileGroovy'.
> BUG! exception in phase 'semantic analysis' in source unit '/Users/ykws/Developer/GitHub/swiftlang/swift-java/buildSrc/src/main/groovy/org/swift/swiftkit/gradle/BuildUtils.groovy' Unsupported class file major version 68

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 10s
1 actionable task: 1 executed

うーん

Unsupported class file major version 68

ykwsykws

Gradle が何かおかしいのかも

% ./gradlew test
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/ykws/.gradle/wrapper/dists/gradle-8.10-bin/deqhafrv1ntovfmgh0nh3npr9/gradle-8.10/lib/native-platform-0.22-milestone-26.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled


> Task :buildSrc:compileGroovy FAILED
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/ykws/.gradle/wrapper/dists/gradle-8.10-bin/deqhafrv1ntovfmgh0nh3npr9/gradle-8.10/lib/native-platform-0.22-milestone-26.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':buildSrc:compileGroovy'.
> BUG! exception in phase 'semantic analysis' in source unit '/Users/ykws/Developer/GitHub/swiftlang/swift-java/buildSrc/src/main/groovy/org/swift/swiftkit/gradle/BuildUtils.groovy' Unsupported class file major version 68

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 2s
1 actionable task: 1 executed
ykwsykws
% ./gradlew --version

------------------------------------------------------------
Gradle 8.10
------------------------------------------------------------

Build time:    2024-08-14 11:07:45 UTC
Revision:      fef2edbed8af1022cefaf44d4c0514c5f89d7b78

Kotlin:        1.9.24
Groovy:        3.0.22
Ant:           Apache Ant(TM) version 1.10.14 compiled on August 16 2023
Launcher JVM:  21.0.7 (Oracle Corporation 21.0.7+8-LTS-245)
Daemon JVM:    /Users/ykws/.sdkman/candidates/java/21.0.7-oracle (no JDK specified, using current Java home)
OS:            Mac OS X 26.0 aarch64

ykwsykws

act のドキュメントには Docker Desktop の案内があるけど CLI でも良い?

If you are using macOS, please be sure to follow the steps outlined in Docker Docs for how to install Docker Desktop for Mac.

ykwsykws

JavaKit: Java コードから Swift のコードを Java ライブラリとして扱って呼び出せるようにする Swift マクロのライブラリ

  • JDK 17+
  • Swift 6.2
% java -version
java version "17.0.12" 2024-07-16 LTS
Java(TM) SE Runtime Environment (build 17.0.12+8-LTS-286)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.12+8-LTS-286, mixed mode, sharing)
% swift -version
swift-driver version: 1.127.8 Apple Swift version 6.2 (swiftlang-6.2.0.13.10 clang-1700.3.13.4)
Target: arm64-apple-macosx26.0

サンプルコードの実行は README の通り

cd Samples/JavaKitSampleApp
./ci-validate.sh

以下の出力が確認できる

Hello from Swift!!!
And hello back from Java! You passed me 42
Swift got back 3.14159 from Java
We expect the above value to be the initial value, 3.14159
Updating Java field value to something different
And hello back from Java! You passed me 17
Swift got back updated 2.71828 from Java
Swift created a new Java instance with the value 3.14159
Hello to Java
Salutations, Swift 👋🏽 How's it going
And hello back from a 🗑️-collected language! You passed me 42
Running a JavaPredicate from swift 3 < 10 = true
Converting doubles to strings: ["3.14159", "2.71828"]
Hello from the subclass!
Salutations, Swift
Salutations, Hello from Swift
Caught Java error: java.lang.Exception: I am an error
Optional text = Optional(JavaKit.JavaString)
Optional string value = Optional("")
Optional text function returned Optional(JavaKit.JavaString)
Optional double function returned Optional(2.0)
Optional long function returned Optional(21)
sayHello(17, 25) = 425

プログラムのエントリポイントは、 JavaKitExample.java#main
HelloSubclass が Swift で実装された HelloSwift を継承している

https://github.com/swiftlang/swift-java/blob/7bec68559498cbdfc18abd288988b737f5b5d69e/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSubclass.java#L17

Swift で定義されたメソッド sayHello を Java からコールしている

https://github.com/swiftlang/swift-java/blob/7bec68559498cbdfc18abd288988b737f5b5d69e/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift#L25

Swift 側では、 @JavaMethod のマクロを利用して Java メソッドとして定義している

https://github.com/swiftlang/swift-java/blob/7bec68559498cbdfc18abd288988b737f5b5d69e/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift#L24

ykwsykws

Java の実装の一部を Swift で実装する手順

  1. Java 側で Swift で実装したいメソッドを native で宣言する
  2. Swift 側で @JavaImplementation で Java class の extesion を用意する(この時の protocol は Javaクラス名+NativeMethods になる。この原理がよくわかっていない)
  3. native 宣言したメソッドを @JavaMethod で実装する
ykwsykws

Swift ライブラリを Java ライブラリとして扱う手順

  1. Swift で実装済みのライブラリがあることが前提
  2. jextract で Swift のライブラリを Java class ファイルに変換
  3. Java コードから変換したクラスファイルを呼び出す