Skip to content

Commit 6967ab5

Browse files
committed
Introduce a SwiftPM plugin to compile Java sources
Add a SwiftPM build tool plugin that compiles all of the Java source files that are part of a given SwiftPM target into class files. Use this within the JavaKit example so we can build everything through SwiftPM.
1 parent b8ee12c commit 6967ab5

File tree

6 files changed

+221
-2
lines changed

6 files changed

+221
-2
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ all:
5858
@echo " javakit-run: Run the JavaKit example program that uses Java libraries from Swift."
5959
@echo " javakit-generate: Regenerate the Swift wrapper code for the various JavaKit libraries from Java. This only has to be done when changing the Java2Swift tool."
6060
@echo " jextract-run: Run the Java example code that uses the wrapped Swift library. NOTE: this requires development toolchain described in the README."
61-
@echo " jextract-generate: Generate Java wrapper code for the example Swift library allowing Swift to be called from Java. NOTE: this requires development toolchain described in the README."
61+
@echo " jextract-generate: Generate Java wrapper code for the example Swift library allowing Swift to be called from Java. NOTE: this requires development toolchain described ein the README."
6262

6363
$(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/Java2Swift:
6464
swift build
6565

6666
javakit-run: $(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/libExampleSwiftLibrary.$(LIB_SUFFIX)
67-
./gradlew Samples:JavaKitSampleApp:run
67+
java -cp .build/plugins/outputs/swift-java/JavaKitExample/destination/JavaCompilerPlugin/Java -Djava.library.path=.build/debug com.example.swift.JavaKitSampleMain
6868

6969
Java2Swift: $(BUILD_DIR)/debug/Java2Swift
7070

Package.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ let package = Package(
8686
targets: ["Java2SwiftTool"]
8787
),
8888

89+
// ==== Plugin for building Java code
90+
.plugin(
91+
name: "JavaCompilerPlugin",
92+
targets: [
93+
"JavaCompilerPlugin"
94+
]
95+
),
96+
8997
// ==== jextract-swift (extract Java accessors from Swift interface files)
9098

9199
.executable(
@@ -197,14 +205,24 @@ let package = Package(
197205
.linkedLibrary("jvm"),
198206
]
199207
),
208+
209+
.plugin(
210+
name: "JavaCompilerPlugin",
211+
capability: .buildTool()
212+
),
213+
200214
.target(
201215
name: "JavaKitExample",
202216
dependencies: ["JavaKit"],
203217
swiftSettings: [
204218
.swiftLanguageMode(.v5),
205219
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
220+
],
221+
plugins: [
222+
.plugin(name: "JavaCompilerPlugin")
206223
]
207224
),
225+
208226
.target(
209227
name: "ExampleSwiftLibrary",
210228
dependencies: [],
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
import PackagePlugin
17+
18+
@main
19+
struct JavaCompilerBuildToolPlugin: BuildToolPlugin {
20+
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
21+
guard let sourceModule = target.sourceModule else { return [] }
22+
23+
// Collect all of the Java source files within this target's sources.
24+
let javaFiles = sourceModule.sourceFiles.map { $0.url }.filter {
25+
$0.pathExtension == "java"
26+
}
27+
if javaFiles.isEmpty {
28+
return []
29+
}
30+
31+
// Note: Target doesn't have a directoryURL counterpart to directory,
32+
// so we cannot eliminate this deprecation warning.
33+
let sourceDir = target.directory.string
34+
35+
// The class files themselves will be generated into the build directory
36+
// for this target.
37+
let classFiles = javaFiles.map { sourceFileURL in
38+
let sourceFilePath = sourceFileURL.path
39+
guard sourceFilePath.starts(with: sourceDir) else {
40+
fatalError("Could not get relative path for source file \(sourceFilePath)")
41+
}
42+
43+
return URL(
44+
filePath: context.pluginWorkDirectoryURL.path
45+
).appending(path: "Java")
46+
.appending(path: String(sourceFilePath.dropFirst(sourceDir.count)))
47+
.deletingPathExtension()
48+
.appendingPathExtension("class")
49+
}
50+
51+
let javaHome = URL(filePath: findJavaHome())
52+
let javaClassFileURL = context.pluginWorkDirectoryURL
53+
.appending(path: "Java")
54+
return [
55+
.buildCommand(
56+
displayName: "Compiling \(javaFiles.count) Java files for target \(sourceModule.name) to \(javaClassFileURL)",
57+
executable: javaHome
58+
.appending(path: "bin")
59+
.appending(path: "javac"),
60+
arguments: javaFiles.map { $0.path(percentEncoded: false) } + [
61+
"-d", javaClassFileURL.path()
62+
],
63+
inputFiles: javaFiles,
64+
outputFiles: classFiles
65+
)
66+
]
67+
}
68+
}
69+
70+
// Note: the JAVA_HOME environment variable must be set to point to where
71+
// Java is installed, e.g.,
72+
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
73+
func findJavaHome() -> String {
74+
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
75+
return home
76+
}
77+
78+
// This is a workaround for envs (some IDEs) which have trouble with
79+
// picking up env variables during the build process
80+
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
81+
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
82+
if let lastChar = home.last, lastChar.isNewline {
83+
return String(home.dropLast())
84+
}
85+
86+
return home
87+
}
88+
89+
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
90+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package com.example.swift;
16+
17+
public class HelloSubclass extends HelloSwift {
18+
private String greeting;
19+
20+
public HelloSubclass(String greeting) {
21+
this.greeting = greeting;
22+
}
23+
24+
private void greetMe() {
25+
super.greet(greeting);
26+
}
27+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package com.example.swift;
16+
17+
public class HelloSwift {
18+
private double value;
19+
private static double initialValue = 3.14159;
20+
private String name = "Java";
21+
22+
static {
23+
System.loadLibrary("JavaKitExample");
24+
}
25+
26+
public HelloSwift() {
27+
this.value = initialValue;
28+
}
29+
30+
public native int sayHello(int x, int y);
31+
public native String throwMessageFromSwift(String message) throws Exception;
32+
33+
// To be called back by the native code
34+
private double sayHelloBack(int i) {
35+
System.out.println("And hello back from " + name + "! You passed me " + i);
36+
return value;
37+
}
38+
39+
public void greet(String name) {
40+
System.out.println("Salutations, " + name);
41+
}
42+
43+
String[] doublesToStrings(double[] doubles) {
44+
int size = doubles.length;
45+
String[] strings = new String[size];
46+
47+
for(int i = 0; i < size; i++) {
48+
strings[i] = "" + doubles[i];
49+
}
50+
51+
return strings;
52+
}
53+
54+
public void throwMessage(String message) throws Exception {
55+
throw new Exception(message);
56+
}
57+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package com.example.swift;
16+
17+
/**
18+
* This sample shows off a {@link HelloSwift} type which is partially implemented in Swift.
19+
* For the Swift implementation refer to
20+
*/
21+
public class JavaKitSampleMain {
22+
23+
public static void main(String[] args) {
24+
int result = new HelloSubclass("Swift").sayHello(17, 25);
25+
System.out.println("sayHello(17, 25) = " + result);
26+
}
27+
}

0 commit comments

Comments
 (0)