As discussed in this issue, Java2Swift errors can be somewhat opaque. I'm building with -j 1 just in case there are any races between the plugins.
I'm finding a few odd issues when trying to wrap some of the Android Flutter classes (WIP here). I suspect I am just holding it wrong.
Java classes generated by JavaCompilerPlugin don't seem to get found unless I explicitly add them to the classPath in Java2Swift.config, or pass as an argument to Java2Swift-tool, both which seem undesirable (and also doesn't appear necessary in the JavaKitSampleApp sample).
I'm not seeing a NativeMethods protocol emitted for a Java class which implements an interface:
package com.padl.AndroidFlutter;
import io.flutter.plugin.common.BinaryMessenger;
import java.nio.ByteBuffer;
public class FlutterSwiftBinaryMessageHandler implements BinaryMessenger.BinaryMessageHandler {
static {
System.loadLibrary("FlutterSwift");
}
public native void onMessage(/* @Nullable */ ByteBuffer message, /* @NonNull */ BinaryMessenger.BinaryReply binaryReply);
}
is only generating the following – I see the inherited methods from the interface, but I'm getting a projected implementation rather than the opportunity to implement it myself. Removing the static constructor doesn't appear to make a difference.
// Auto-generated by Java-to-Swift wrapper generator.
import JavaKit
import JavaRuntime
@JavaClass("com.padl.AndroidFlutter.FlutterSwiftBinaryMessageHandler", extends: JavaObject.self, implements: AndroidFlutterBinaryMessenger.BinaryMessageHandler.self)
public struct _FlutterSwiftBinaryMessageHandler {
@JavaMethod
public init(environment: JNIEnvironment? = nil)
@JavaMethod
public func onMessage(_ message: JavaNIOByteBuffer?, _ binaryReply: AndroidFlutterBinaryMessenger.BinaryReply?)
...
}
I think that’s correct purely by Java rules: you need a native declaration to say the methods will be implemented through JNI, independent of whether they’re also satisfying an interface. Unfortunate boilerplate, but I believe required nonetheless.
Noted, my question was to do with Swift2Java rather than Java itself - perhaps I could have used better terms
PS. Anyone here old enough to remember the jobs tool that shipped in Rhapsody (and WebObjects)? There was a moment where a bunch of first party apps were written in Java. The NeXT folk were incredible at cross-language interop, in part due to the dynamism of the ObjC runtime, and in part due to being really smart. End history tidbit!
We have yet to make it easy to help you hold it correctly! Your feedback is definitely helping us do so.
I'll briefly describe how it is supposed to work, then take some wild guesses about what's going wrong.
The current scheme is a bit... magical. The JavaCompilerPlugin will build all of the Java source files within the sources directory of your SwiftPM target. It expects those Java source files to have directory structure matching the package structure, e.g., org/swift/example/MyClass.java for org.swift.example.MyClass. The Java2SwiftPlugin will pick up the class files that were built with JavaCompilerPlugin in that target and treat those classes as having their native implementations be something that should be implemented in Swift. That's what triggers the *NativeMethods protocol to be built.
There are two undiagnosed conditions that can easily break this:
JavaCompilerPlugin is after Java2SwiftPlugin or in a different target. Java compilation has to come first.
The Java source files aren't in the "right" places to match the package structure, so we can't predict the generated .class files.
We definitely want to take away these foot guns or at least diagnose them, but haven't figured out quite how just yet.
Gah, somehow I edited my reply to post a follow up question. Restoring the gist of the original reply: I had thought of the footguns you mentioned, in the end I just hand coded the Swift projections for the native methods.
I think the problem is that JavaCompilerPlugin isn't run before Java2SwiftPlugin is run. One or the other works (or compiling Java sources that are not used by Java2SwiftPlugin, and having Java2SwiftPlugin project external classes). I have the plugins defined in the correct order I believe:
package com.padl.counter;
public class SwiftObjectHolder {
public long swiftObject;
}
I see nothing generated:
% ls Counter/destination/*
Counter/destination/Java2SwiftPlugin:
generated
Counter/destination/JavaCompilerPlugin:
and I do see the ’helpful’ error:
% ./build-counter-android.sh
Building for debugging...
warning: 'flutterswift': Source files for target CxxFlutterSwift should be located under 'Sources/CxxFlutterSwift', or a custom sources path can be set with the 'path' property in Package.swift
Error: com.padl.counter.SwiftObjectHolder
[0/32] Wrapping 2 Java classes target Counter in Swift
In other news, here's a fun error which makes absolutely no sense, running after a clean build:
./build-counter-android.sh
Building for debugging...
warning: 'flutterswift': Source files for target CxxFlutterSwift should be located under 'Sources/CxxFlutterSwift', or a custom sources path can be set with the 'path' property in Package.swift
error: No target named 'Java2Swift-arm64-apple-macosx14.0-debug.exe' in build description
Switching from invoking "${TOOLCHAINS}/usr/bin/swift" build to swift build --toolchain ${TOOLCHAINS}seems to fix it. I am building on macOS 14.5 with Xcode 16.1.
Another fun fact, accidental trailing space in classPath:
Error occurred during initialization of VM
java.lang.InternalError: Bad level: 2
at jdk.internal.misc.VM.initLevel(java.base@21.0.3/Unknown Source)
at java.lang.System.initPhase2(java.base@21.0.3/Unknown Source)
Not necessarily Java2Swift's responsibility to check but, could be nice.
Final fun fact: switching to the macOS Swift driver and (unfortunately) including the .build/plugins/outputs/flutterswift/${product}/destination/JavaCompilerPlugin/Java direction in Java2Swift.config seems too have half-resolved the issue described in the previous post, but it's ignoring the native methods and not emitting the protocol. Which erm was precisely what I described in the initial post. I’ll keep digging!
I'm not familiar with the SwiftPM plugin architecture, but adding some debugging Java2SwiftPlugin suggests the underlying issue is sourceModule.pluginGeneratedResources being an empty array. As such it is not finding the class files compiled by JavaCompilerPlugin, which are the ones that are subject to --swift-native-implementation.
So its sounds like there's something about my Package.swift which is messing up the plugin pipeline.
Alright: here is the fix. :) Package.swift needs to be swift-tools-version:6.0 (necessitating .swiftLanguageMode(.v5)). Footgun alert! I'll file an issue.
It's a surprise to me that this is gated on the Swift tools version. I see that you filed issues for these problems and limitations you encountered--thank you!