Skip to content

Commit c33070c

Browse files
committed
WIP: fixing up wrap-java behavior, also adding tests which compile and
extract, just like the command does
1 parent 3d9433f commit c33070c

21 files changed

+643
-137
lines changed

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ let package = Package(
197197

198198
],
199199
dependencies: [
200+
.package(url: "https://github.com/apple/swift-log", from: "1.2.0"),
201+
200202
.package(url: "https://github.com/swiftlang/swift-syntax", from: "601.0.1"),
201203
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
202204
.package(url: "https://github.com/apple/swift-system", from: "1.4.0"),
@@ -377,6 +379,7 @@ let package = Package(
377379
.target(
378380
name: "SwiftJavaToolLib",
379381
dependencies: [
382+
.product(name: "Logging", package: "swift-log"),
380383
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
381384
.product(name: "SwiftSyntax", package: "swift-syntax"),
382385
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
@@ -400,6 +403,7 @@ let package = Package(
400403
.executableTarget(
401404
name: "SwiftJavaTool",
402405
dependencies: [
406+
.product(name: "Logging", package: "swift-log"),
403407
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
404408
.product(name: "SwiftSyntax", package: "swift-syntax"),
405409
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 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+
import org.junit.jupiter.api.Test;
18+
import org.swift.swiftkit.core.SwiftArena;
19+
20+
import java.util.Optional;
21+
import java.util.OptionalDouble;
22+
import java.util.OptionalInt;
23+
import java.util.OptionalLong;
24+
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
28+
public class ThrowsFFMTest {
29+
@Test
30+
void throwSwiftError() {
31+
try (var arena = SwiftArena.ofConfined()) {
32+
var funcs = ThrowingFuncs.init(12, arena);
33+
funcs.throwError();
34+
} catch (Exception ex) {
35+
assertEquals("java.lang.Exception: MyExampleSwiftError(message: \"yes, it\\'s an error!\")", ex.toString());
36+
return;
37+
}
38+
39+
throw new AssertionError("Expected Swift error to be thrown as exception");
40+
}
41+
}

Sources/SwiftJavaTool/String+Extensions.swift renamed to Sources/SwiftJava/String+Extensions.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,15 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Foundation
16-
import ArgumentParser
17-
import SwiftJavaToolLib
18-
import SwiftJava
19-
import JavaUtilJar
20-
import SwiftJavaToolLib
21-
import SwiftJavaConfigurationShared
16+
// import SwiftJavaToolLib
17+
// import SwiftJava
18+
// import JavaUtilJar
19+
// import SwiftJavaConfigurationShared
2220

2321
extension String {
2422
/// For a String that's of the form java.util.Vector, return the "Vector"
2523
/// part.
26-
var defaultSwiftNameForJavaClass: String {
24+
package var defaultSwiftNameForJavaClass: String {
2725
if let dotLoc = lastIndex(of: ".") {
2826
let afterDot = index(after: dotLoc)
2927
return String(self[afterDot...]).javaClassNameToCanonicalName.adjustedSwiftTypeName
@@ -36,12 +34,12 @@ extension String {
3634
extension String {
3735
/// Replace all of the $'s for nested names with "." to turn a Java class
3836
/// name into a Java canonical class name,
39-
var javaClassNameToCanonicalName: String {
37+
package var javaClassNameToCanonicalName: String {
4038
return replacing("$", with: ".")
4139
}
4240

4341
/// Whether this is the name of an anonymous class.
44-
var isLocalJavaClass: Bool {
42+
package var isLocalJavaClass: Bool {
4543
for segment in split(separator: "$") {
4644
if let firstChar = segment.first, firstChar.isNumber {
4745
return true
@@ -52,7 +50,7 @@ extension String {
5250
}
5351

5452
/// Adjust type name for "bad" type names that don't work well in Swift.
55-
var adjustedSwiftTypeName: String {
53+
package var adjustedSwiftTypeName: String {
5654
switch self {
5755
case "Type": return "JavaType"
5856
default: return self

Sources/SwiftJavaConfigurationShared/Configuration.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public struct Configuration: Codable {
5656
memoryManagementMode ?? .default
5757
}
5858

59-
// ==== java 2 swift ---------------------------------------------------------
59+
// ==== wrap-java ---------------------------------------------------------
6060

6161
/// The Java class path that should be passed along to the swift-java tool.
6262
public var classpath: String? = nil
@@ -76,6 +76,10 @@ public struct Configuration: Codable {
7676
// Generate class files suitable for the specified Java SE release.
7777
public var targetCompatibility: JavaVersion?
7878

79+
/// Filter input Java types by their package prefix if set.
80+
/// Can be overriden by `--filter-java-package`.
81+
public var filterJavaPackage: String?
82+
7983
// ==== dependencies ---------------------------------------------------------
8084

8185
// Java dependencies we need to fetch for this target.

Sources/SwiftJavaTool/Commands/ConfigureCommand.swift

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import Logging
1516
import ArgumentParser
1617
import Foundation
1718
import SwiftJavaToolLib
@@ -27,6 +28,9 @@ import SwiftJavaShared
2728

2829
extension SwiftJava {
2930
struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions {
31+
32+
static let log: Logging.Logger = Logger(label: "swift-java:\(configuration.commandName!)")
33+
3034
static let configuration = CommandConfiguration(
3135
commandName: "configure",
3236
abstract: "Configure and emit a swift-java.config file based on an input dependency or jar file")
@@ -63,12 +67,12 @@ extension SwiftJava {
6367
extension SwiftJava.ConfigureCommand {
6468
mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
6569
let classpathEntries = self.configureCommandJVMClasspath(
66-
searchDirs: [self.effectiveSwiftModuleURL], config: config)
70+
searchDirs: [self.effectiveSwiftModuleURL], config: config, log: Self.log)
6771

6872
let jvm =
6973
try self.makeJVM(classpathEntries: classpathEntries)
7074

71-
try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment())
75+
try emitConfiguration(classpathEntries: classpathEntries, environment: jvm.environment())
7276
}
7377

7478
/// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration.
@@ -93,32 +97,42 @@ extension SwiftJava.ConfigureCommand {
9397

9498
// TODO: make this perhaps "emit type mappings"
9599
mutating func emitConfiguration(
96-
classpath: [String],
100+
classpathEntries: [String],
97101
environment: JNIEnvironment
98102
) throws {
103+
var log = Self.log
104+
log.logLevel = .init(rawValue: self.logLevel.rawValue)!
105+
106+
107+
log.info("Run: emit configuration...")
108+
var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite()
109+
99110
if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage {
100-
print("[java-swift][debug] Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)")
111+
log.debug("Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)")
112+
} else if let filterJavaPackage = configuration.filterJavaPackage {
113+
// take the package filter from the configuration file
114+
self.commonJVMOptions.filterJavaPackage = filterJavaPackage
115+
} else {
116+
log.debug("Generate Java->Swift type mappings. No package filter applied.")
101117
}
102-
print("[java-swift][debug] Classpath: \(classpath)")
118+
log.debug("Classpath: \(classpathEntries)")
103119

104-
if classpath.isEmpty {
105-
print("[java-swift][warning] Classpath is empty!")
120+
if classpathEntries.isEmpty {
121+
log.warning("Classpath is empty!")
106122
}
107123

108124
// Get a fresh or existing configuration we'll amend
109-
var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite()
110125
if amendExistingConfig {
111-
print("[swift-java] Amend existing swift-java.config file...")
126+
log.info("Amend existing swift-java.config file...")
112127
}
113-
configuration.classpath = classpath.joined(separator: ":") // TODO: is this correct?
128+
configuration.classpath = classpathEntries.joined(separator: ":") // TODO: is this correct?
114129

115130
// Import types from all the classpath entries;
116131
// Note that we use the package level filtering, so users have some control over what gets imported.
117-
let classpathEntries = classpath.split(separator: ":").map(String.init)
118132
for entry in classpathEntries {
119133
guard fileOrDirectoryExists(at: entry) else {
120134
// We only log specific jars missing, as paths may be empty directories that won't hurt not existing.
121-
print("[debug][swift-java] Classpath entry does not exist: \(entry)")
135+
log.debug("Classpath entry does not exist: \(entry)")
122136
continue
123137
}
124138

@@ -131,9 +145,9 @@ extension SwiftJava.ConfigureCommand {
131145
environment: environment
132146
)
133147
} else if FileManager.default.fileExists(atPath: entry) {
134-
print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)")
148+
log.warning("Currently unable handle directory classpath entries for config generation! Skipping: \(entry)")
135149
} else {
136-
print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)")
150+
log.warning("Classpath entry does not exist, skipping: \(entry)")
137151
}
138152
}
139153

@@ -154,6 +168,8 @@ extension SwiftJava.ConfigureCommand {
154168
forJar jarFile: JarFile,
155169
environment: JNIEnvironment
156170
) throws {
171+
let log = Self.log
172+
157173
for entry in jarFile.entries()! {
158174
// We only look at class files in the Jar file.
159175
guard entry.getName().hasSuffix(".class") else {
@@ -179,6 +195,7 @@ extension SwiftJava.ConfigureCommand {
179195
let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
180196
.dropLast(".class".count))
181197

198+
182199
if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage,
183200
!javaCanonicalName.hasPrefix(filterJavaPackage) {
184201
// Skip classes which don't match our expected prefix
@@ -191,7 +208,17 @@ extension SwiftJava.ConfigureCommand {
191208
continue
192209
}
193210

194-
configuration.classes?[javaCanonicalName] =
211+
if configuration.classes == nil {
212+
configuration.classes = [:]
213+
}
214+
215+
if let configuredSwiftName = configuration.classes![javaCanonicalName] {
216+
log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.")
217+
} else {
218+
log.info("Configure Java type '\(javaCanonicalName)' as '\(javaCanonicalName.defaultSwiftNameForJavaClass.bold)' Swift type.")
219+
}
220+
221+
configuration.classes![javaCanonicalName] =
195222
javaCanonicalName.defaultSwiftNameForJavaClass
196223
}
197224
}

Sources/SwiftJavaTool/Commands/JExtractCommand.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ extension SwiftJava.JExtractCommand {
117117
if self.mode == .jni {
118118
switch self.unsignedNumbers {
119119
case .annotate:
120-
throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)")
121-
case .wrapGuava:
122120
() // OK
121+
case .wrapGuava:
122+
throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)")
123123
}
124124
} else if self.mode == .ffm {
125125
guard self.memoryManagementMode == .explicit else {

Sources/SwiftJavaTool/Commands/ResolveCommand.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ extension SwiftJava.ResolveCommand {
104104
let deps = dependencies.map { $0.descriptionGradleStyle }
105105
print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)")
106106

107-
let dependenciesClasspath = await resolveDependencies(dependencies: dependencies)
107+
let workDir = URL(fileURLWithPath: self.commonOptions.outputDirectory!)
108+
.appending(path: "resolver-dir")
109+
110+
let dependenciesClasspath = await resolveDependencies(workDir: workDir, dependencies: dependencies)
108111
let classpathEntries = dependenciesClasspath.split(separator: ":")
109112

110113
print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "")
@@ -122,10 +125,15 @@ extension SwiftJava.ResolveCommand {
122125
///
123126
/// - Parameter dependencies: maven-style dependencies to resolve
124127
/// - Returns: Colon-separated classpath
125-
func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String {
126-
let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
127-
.appendingPathComponent(".build")
128-
let resolverDir = try! createTemporaryDirectory(in: workDir)
128+
func resolveDependencies(workDir: URL, dependencies: [JavaDependencyDescriptor]) async -> String {
129+
print("Create directory: \(workDir.absoluteString)")
130+
131+
let resolverDir: URL
132+
do {
133+
resolverDir = try createTemporaryDirectory(in: workDir)
134+
} catch {
135+
fatalError("Unable to create temp directory at: \(workDir.absoluteString)! \(error)")
136+
}
129137
defer {
130138
try? FileManager.default.removeItem(at: resolverDir)
131139
}
@@ -162,7 +170,9 @@ extension SwiftJava.ResolveCommand {
162170
} else {
163171
let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'."
164172
fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" +
165-
"Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "<empty>")>>>")
173+
"Command was: \(CommandLine.arguments.joined(separator: " ").bold)\n" +
174+
"Output was: <<<\(outString)>>>;\n" +
175+
"Err was: <<<\(errString)>>>")
166176
}
167177

168178
return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count))

Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@
1414

1515
import Foundation
1616
import ArgumentParser
17+
import Logging
1718
import SwiftJavaToolLib
1819
import SwiftJava
1920
import JavaUtilJar
20-
import SwiftJavaToolLib
2121
import SwiftJavaConfigurationShared
2222

2323
extension SwiftJava {
2424

2525
struct WrapJavaCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions {
26+
27+
static let log: Logging.Logger = .init(label: "swift-java:\(configuration.commandName!)")
28+
2629
static let configuration = CommandConfiguration(
2730
commandName: "wrap-java",
2831
abstract: "Wrap Java classes with corresponding Swift bindings.")
@@ -74,7 +77,7 @@ extension SwiftJava.WrapJavaCommand {
7477
print("[trace][swift-java] INPUT: \(input)")
7578

7679
var classpathEntries = self.configureCommandJVMClasspath(
77-
searchDirs: classpathSearchDirs, config: config)
80+
searchDirs: classpathSearchDirs, config: config, log: Self.log)
7881

7982
// Load all of the dependent configurations and associate them with Swift modules.
8083
let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn).map { moduleName, config in
@@ -136,12 +139,21 @@ extension SwiftJava.WrapJavaCommand {
136139
translator.addConfiguration(config, forSwiftModule: effectiveSwiftModule)
137140

138141
// Load all of the explicitly-requested classes.
139-
let classLoader = try JavaClass<ClassLoader>(environment: environment)
142+
let classLoader = try! JavaClass<ClassLoader>(environment: environment)
140143
.getSystemClassLoader()!
141144
var javaClasses: [JavaClass<JavaObject>] = []
142145
for (javaClassName, _) in config.classes ?? [:] {
146+
147+
if let filterPackage = config.filterJavaPackage {
148+
if javaClassName.starts(with: filterPackage) {
149+
log.info("SKIP Wrapping java type: \(javaClassName)")
150+
continue
151+
}
152+
}
153+
log.info("Wrapping java type: \(javaClassName)")
154+
143155
guard let javaClass = try classLoader.loadClass(javaClassName) else {
144-
print("warning: could not find Java class '\(javaClassName)'")
156+
log.warning("Could not load Java class '\(javaClassName)', skipping.")
145157
continue
146158
}
147159

@@ -175,18 +187,18 @@ extension SwiftJava.WrapJavaCommand {
175187
return nil
176188
}
177189

190+
178191
// If this class has been explicitly mentioned, we're done.
179-
if translator.translatedClasses[javaClassName] != nil {
192+
guard translator.translatedClasses[javaClassName] == nil else {
180193
return nil
181194
}
182195

183196
// Record this as a translated class.
184197
let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName
185198
.defaultSwiftNameForJavaClass
186199

187-
188200
let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)"
189-
translator.translatedClasses[javaClassName] = (swiftName, nil)
201+
translator.translatedClasses[javaClassName] = SwiftTypeName(module: nil, name: swiftName)
190202
return nestedClass
191203
}
192204

0 commit comments

Comments
 (0)