Skip to content

Commit 823d685

Browse files
committed
work on swiftpm plugin
1 parent 784ec9c commit 823d685

File tree

12 files changed

+248
-82
lines changed

12 files changed

+248
-82
lines changed

Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
152152
}
153153
log("Found swift-java at \(swiftJavaDirectory)")
154154

155+
let swiftKitCoreClassPath = swiftJavaDirectory.appending(path: "SwiftKitCore/build/classes/java/main")
156+
155157
commands += [
156158
.buildCommand(
157159
displayName: "Build SwiftKitCore",
@@ -164,19 +166,20 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
164166
],
165167
environment: [:],
166168
inputFiles: [swiftJavaDirectory],
167-
outputFiles: []
169+
outputFiles: [swiftKitCoreClassPath]
168170
)
169171
]
170172

171-
let swiftKitCoreClassPath = swiftJavaDirectory.appending(path: "SwiftKitCore/build/classes/java/main")
172-
173173
// Compile the jextracted sources
174174
let javaHome = URL(filePath: findJavaHome())
175175
#if os(Windows)
176176
let javac = "javac.exe"
177+
let jar = "jar.exe"
177178
#else
178179
let javac = "javac"
180+
let jar = "jar"
179181
#endif
182+
180183
commands += [
181184
.buildCommand(
182185
displayName: "Build extracted Java sources",
@@ -185,12 +188,68 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
185188
.appending(path: javac),
186189
arguments: [
187190
"@\(javaSourcesFile.path(percentEncoded: false))",
188-
"-d", javaClassFileURL.path,
191+
"-d", javaClassFileURL.path(percentEncoded: false),
189192
"-parameters",
190193
"-classpath", swiftKitCoreClassPath.path(percentEncoded: false)
191194
],
192-
inputFiles: [javaSourcesFile],
193-
outputFiles: []
195+
inputFiles: [javaSourcesFile, swiftKitCoreClassPath],
196+
outputFiles: [javaClassFileURL]
197+
)
198+
]
199+
200+
// Wrap into JAR that we can use `swift-java configure` on
201+
// let jarFileURL = context.pluginWorkDirectoryURL.appending(path: "generated-sources.jar")
202+
//
203+
// commands += [
204+
// .buildCommand(
205+
// displayName: "Wrap compiled Java sources into .jar",
206+
// executable: javaHome
207+
// .appending(path: "bin")
208+
// .appending(path: jar),
209+
// arguments: [
210+
// "--create",
211+
// "--file", jarFileURL.path(percentEncoded: false),
212+
// "-C", javaClassFileURL.path(percentEncoded: false),
213+
// "."
214+
// ],
215+
// inputFiles: [javaClassFileURL],
216+
// outputFiles: [jarFileURL]
217+
// )
218+
// ]
219+
220+
// Run `configure` to extract a swift-java config to use for wrap-java
221+
let swiftJavaConfigURL = context.pluginWorkDirectoryURL.appending(path: "swift-java.config")
222+
223+
commands += [
224+
.buildCommand(
225+
displayName: "Wrap compiled Java sources using wrap-java",
226+
executable: toolURL,
227+
arguments: [
228+
"configure",
229+
"--output-directory", context.pluginWorkDirectoryURL.path(percentEncoded: false),
230+
"--cp", javaClassFileURL.path(percentEncoded: false),
231+
"--swift-module", sourceModule.name,
232+
"--swift-prefix", "Java"
233+
],
234+
inputFiles: [javaClassFileURL],
235+
outputFiles: [swiftJavaConfigURL]
236+
)
237+
]
238+
239+
// In the end we can run wrap-java on the previous inputs
240+
commands += [
241+
.buildCommand(
242+
displayName: "Wrap compiled Java sources using wrap-java",
243+
executable: toolURL,
244+
arguments: [
245+
"wrap-java",
246+
"--swift-module", sourceModule.name,
247+
"--output-directory", outputSwiftDirectory.path(percentEncoded: false),
248+
"--config", swiftJavaConfigURL.path(percentEncoded: false),
249+
"--cp", swiftKitCoreClassPath.path(percentEncoded: false),
250+
],
251+
inputFiles: [swiftJavaConfigURL, swiftKitCoreClassPath],
252+
outputFiles: [outputSwiftDirectory.appending(path: "JavaStorage.swift")]
194253
)
195254
]
196255

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Storage.swift

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,3 @@ struct JavaJNISwiftInstance {
7575
@JavaMethod("$memoryAddress")
7676
public func memoryAddress() -> Int64
7777
}
78-
79-
@JavaClass("com.example.swift.StorageItem", implements: JavaJNISwiftInstance.self)
80-
open class JavaStorageItem: JavaObject {}
81-
82-
extension JavaClass<JavaStorageItem> {
83-
@JavaStaticMethod
84-
public func wrapMemoryAddressUnsafe(selfPointer: Int64) -> JavaStorageItem!
85-
}
86-
87-
@JavaInterface("com.example.swift.Storage")
88-
struct JavaStorage {
89-
@JavaMethod
90-
func load() -> JavaStorageItem!
91-
92-
@JavaMethod
93-
func save(item: JavaStorageItem!)
94-
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 SwiftJava
16+
17+
extension JavaClass {
18+
/// Whether this is a 'public' class.
19+
public var isPublic: Bool {
20+
return (getModifiers() & 0x00000001) != 0
21+
}
22+
23+
/// Whether this is a 'private' class.
24+
public var isPrivate: Bool {
25+
return (getModifiers() & 0x00000002) != 0
26+
}
27+
28+
/// Whether this is a 'protected' class.
29+
public var isProtected: Bool {
30+
return (getModifiers() & 0x00000004) != 0
31+
}
32+
33+
/// Whether this is a 'package' method.
34+
///
35+
/// The "default" access level in Java is 'package', it is signified by lack of a different access modifier.
36+
public var isPackage: Bool {
37+
return !isPublic && !isPrivate && !isProtected
38+
}
39+
}

Sources/SwiftJava/String+Extensions.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ extension String {
2727
}
2828

2929
extension String {
30-
/// Replace all of the $'s for nested names with "." to turn a Java class
31-
/// name into a Java canonical class name,
30+
/// Convert a Java class name to its canonical name.
31+
/// Replaces `$` with `.` for nested classes but preserves `$` at the start of identifiers.
3232
package var javaClassNameToCanonicalName: String {
33-
return replacing("$", with: ".")
33+
self.replacingOccurrences(
34+
of: #"(?<=\w)\$"#,
35+
with: ".",
36+
options: .regularExpression
37+
)
3438
}
3539

3640
/// Whether this is the name of an anonymous class.

Sources/SwiftJavaConfigurationShared/Configuration.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,11 @@ public func readConfiguration(sourceDir: String, file: String = #fileID, line: U
162162
/// Configuration is expected to be "JSON-with-comments".
163163
/// Specifically "//" comments are allowed and will be trimmed before passing the rest of the config into a standard JSON parser.
164164
public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration? {
165-
guard let configData = try? Data(contentsOf: configPath) else {
165+
let configData: Data
166+
do {
167+
configData = try Data(contentsOf: configPath)
168+
} catch {
169+
print("Failed to read SwiftJava configuration at '\(configPath.absoluteURL)', error: \(error)")
166170
return nil
167171
}
168172

Sources/SwiftJavaTool/Commands/ConfigureCommand.swift

Lines changed: 70 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ extension SwiftJava {
5959
swiftModule
6060
}
6161

62-
@Argument(help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file.")
63-
var input: String?
62+
@Option(help: "A prefix that will be added to the names of the Swift types")
63+
var swiftPrefix: String?
6464
}
6565
}
6666

@@ -137,14 +137,19 @@ extension SwiftJava.ConfigureCommand {
137137

138138
print("[debug][swift-java] Importing classpath entry: \(entry)")
139139
if entry.hasSuffix(".jar") {
140+
print("[debug][swift-java] Importing classpath as JAR file: \(entry)")
140141
let jarFile = try JarFile(entry, false, environment: environment)
141142
try addJavaToSwiftMappings(
142143
to: &config,
143144
forJar: jarFile,
144145
environment: environment
145146
)
146-
} else if FileManager.default.fileExists(atPath: entry) {
147-
log.warning("Currently unable handle directory classpath entries for config generation! Skipping: \(entry)")
147+
} else if FileManager.default.fileExists(atPath: entry), let entryURL = URL(string: entry) {
148+
print("[debug][swift-java] Importing classpath as directory: \(entryURL)")
149+
try addJavaToSwiftMappings(
150+
to: &configuration,
151+
forDirectory: entryURL
152+
)
148153
} else {
149154
log.warning("Classpath entry does not exist, skipping: \(entry)")
150155
}
@@ -162,62 +167,82 @@ extension SwiftJava.ConfigureCommand {
162167
)
163168
}
164169

170+
mutating func addJavaToSwiftMappings(
171+
to configuration: inout Configuration,
172+
forDirectory url: Foundation.URL
173+
) throws {
174+
let enumerator = FileManager.default.enumerator(atPath: url.path())
175+
176+
while let filePath = enumerator?.nextObject() as? String {
177+
try addJavaToSwiftMappings(to: &configuration, fileName: filePath)
178+
}
179+
}
180+
165181
mutating func addJavaToSwiftMappings(
166182
to configuration: inout Configuration,
167183
forJar jarFile: JarFile,
168184
environment: JNIEnvironment
169185
) throws {
170-
let log = Self.log
171-
172186
for entry in jarFile.entries()! {
173-
// We only look at class files in the Jar file.
174-
guard entry.getName().hasSuffix(".class") else {
175-
continue
176-
}
187+
try addJavaToSwiftMappings(to: &configuration, fileName: entry.getName())
188+
}
189+
}
177190

178-
// Skip some "common" files we know that would be duplicated in every jar
179-
guard !entry.getName().hasPrefix("META-INF") else {
180-
continue
181-
}
182-
guard !entry.getName().hasSuffix("package-info") else {
183-
continue
184-
}
185-
guard !entry.getName().hasSuffix("package-info.class") else {
186-
continue
187-
}
191+
mutating func addJavaToSwiftMappings(
192+
to configuration: inout Configuration,
193+
fileName: String
194+
) throws {
195+
// We only look at class files
196+
guard fileName.hasSuffix(".class") else {
197+
return
198+
}
188199

189-
// If this is a local class, it cannot be mapped into Swift.
190-
if entry.getName().isLocalJavaClass {
191-
continue
192-
}
200+
// Skip some "common" files we know that would be duplicated in every jar
201+
guard !fileName.hasPrefix("META-INF") else {
202+
return
203+
}
204+
guard !fileName.hasSuffix("package-info") else {
205+
return
206+
}
207+
guard !fileName.hasSuffix("package-info.class") else {
208+
return
209+
}
193210

194-
let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
195-
.dropLast(".class".count))
211+
// If this is a local class, it cannot be mapped into Swift.
212+
if fileName.isLocalJavaClass {
213+
return
214+
}
196215

197-
guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else {
198-
log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters")
199-
continue
200-
}
216+
let javaCanonicalName = String(fileName.replacing("/", with: ".")
217+
.dropLast(".class".count))
201218

202-
if configuration.classes?[javaCanonicalName] != nil {
203-
// We never overwrite an existing class mapping configuration.
204-
// E.g. the user may have configured a custom name for a type.
205-
continue
206-
}
219+
guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else {
220+
log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters")
221+
return
222+
}
207223

208-
if configuration.classes == nil {
209-
configuration.classes = [:]
210-
}
224+
if configuration.classes?[javaCanonicalName] != nil {
225+
// We never overwrite an existing class mapping configuration.
226+
// E.g. the user may have configured a custom name for a type.
227+
return
228+
}
211229

212-
if let configuredSwiftName = configuration.classes![javaCanonicalName] {
213-
log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.")
214-
} else {
215-
log.info("Configure Java type '\(javaCanonicalName)' as '\(javaCanonicalName.defaultSwiftNameForJavaClass.bold)' Swift type.")
216-
}
230+
if configuration.classes == nil {
231+
configuration.classes = [:]
232+
}
217233

218-
configuration.classes![javaCanonicalName] =
219-
javaCanonicalName.defaultSwiftNameForJavaClass
234+
var swiftName = javaCanonicalName.defaultSwiftNameForJavaClass
235+
if let swiftPrefix {
236+
swiftName = "\(swiftPrefix)\(swiftName)"
220237
}
238+
239+
if let configuredSwiftName = configuration.classes![javaCanonicalName] {
240+
log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.")
241+
} else {
242+
log.info("Configure Java type '\(javaCanonicalName)' as '\(swiftName.bold)' Swift type.")
243+
}
244+
245+
configuration.classes![javaCanonicalName] = swiftName
221246
}
222247
}
223248

Sources/SwiftJavaTool/Commands/JExtractCommand.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ extension SwiftJava {
8181

8282
@Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.")
8383
var asyncFuncMode: JExtractAsyncFuncMode?
84+
85+
@Flag
86+
var swiftJavaConfigMode = false
8487
}
8588
}
8689

0 commit comments

Comments
 (0)